diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..9bfc20d1 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,59 @@ +# Copyright 2019 The Crashpad Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +*.S text eol=lf +*.asm text eol=lf +*.c text eol=lf +*.cc text eol=lf +*.cmx text eol=lf +*.css text eol=lf +*.defs text eol=lf +*.doxy text eol=lf +*.gn text eol=lf +*.gni text eol=lf +*.go text eol=lf +*.gyp text eol=lf +*.gypi text eol=lf +*.h text eol=lf +*.m text eol=lf +*.md text eol=lf +*.mm text eol=lf +*.pem text eol=lf +*.plist text eol=lf +*.proctype text eol=lf +*.py text eol=lf +*.sh text eol=lf +*.sym text eol=lf +*.txt text eol=lf +*.yaml text eol=lf +.clang-format text eol=lf +.gitattributes text eol=lf +.gitignore text eol=lf +.vpython text eol=lf +/AUTHORS text eol=lf +/CONTRIBUTORS text eol=lf +/LICENSE text eol=lf +/codereview.settings text eol=lf +APPLE_LICENSE text eol=lf +COPYING.LIB text eol=lf +DEPS text eol=lf +README text eol=lf +README.crashpad text eol=lf + +*.dat binary +*.dll binary +*.ico binary +*.obj binary +*.png binary +*.so binary diff --git a/.gitignore b/.gitignore index 982a95e7..fe8a94c7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,17 @@ +# Copyright 2014 The Crashpad Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + *.Makefile *.ninja *.pyc @@ -10,6 +24,7 @@ .gdbinit /Makefile /out +/third_party/edo/edo /third_party/fuchsia/.cipd /third_party/fuchsia/clang /third_party/fuchsia/qemu @@ -19,6 +34,7 @@ /third_party/linux/.cipd /third_party/linux/clang /third_party/linux/sysroot +/third_party/lss/lss /third_party/gyp/gyp /third_party/mini_chromium/mini_chromium /third_party/zlib/zlib diff --git a/.style.yapf b/.style.yapf new file mode 100644 index 00000000..8aaf9b62 --- /dev/null +++ b/.style.yapf @@ -0,0 +1,16 @@ +# Copyright 2020 The Crashpad Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +[style] +based_on_style = google diff --git a/.vpython b/.vpython new file mode 100644 index 00000000..c2001fc5 --- /dev/null +++ b/.vpython @@ -0,0 +1,32 @@ +# Copyright 2018 The Crashpad Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is a vpython "spec" file. +# +# It describes patterns for python wheel dependencies of the python scripts. +# +# Read more about `vpython` and how to modify this file here: +# https://chromium.googlesource.com/infra/infra/+/master/doc/users/vpython.md + +# This is needed for snapshot/win/end_to_end_test.py. +wheel: < + name: "infra/python/wheels/pypiwin32/${vpython_platform}" + version: "version:219" + match_tag: < + platform: "win32" + > + match_tag: < + platform: "win_amd64" + > +> diff --git a/BUILD.gn b/BUILD.gn index 065a5e1f..98218af1 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -14,29 +14,71 @@ import("build/crashpad_buildconfig.gni") import("build/test.gni") +import("util/net/tls.gni") config("crashpad_config") { include_dirs = [ "." ] } +# TODO(fuchsia:46805): Remove this once instances of UB have been cleaned up. +config("disable_ubsan") { + if (crashpad_is_in_fuchsia) { + cflags = [ "-fno-sanitize=undefined" ] + } + visibility = [ + "snapshot:snapshot", + "minidump:minidump_test", + "third_party/gtest:gtest", + "util:util", + ] +} + if (crashpad_is_in_chromium || crashpad_is_in_fuchsia) { test("crashpad_tests") { deps = [ "client:client_test", - "handler:handler_test", "minidump:minidump_test", - "snapshot:snapshot_test", "test:gmock_main", "test:test_test", - "util:util_test", ] + if (!crashpad_is_ios) { + deps += [ "snapshot:snapshot_test" ] + } + if (!crashpad_is_ios && !crashpad_is_fuchsia) { + deps += [ "handler:handler_test" ] + } + if (crashpad_is_in_fuchsia) { + # TODO(fuchsia:46559): Fix the leaks and remove this. + deps += [ "//build/config/sanitizers:suppress-lsan.DO-NOT-USE-THIS" ] + } + if (crashpad_is_android) { + use_raw_android_executable = true + + copy("crashpad_test_data") { + testonly = true + sources = [ + "test/test_paths_test_data_root.txt", + "util/net/testdata/ascii_http_body.txt", + "util/net/testdata/binary_http_body.dat", + ] + + outputs = [ "$root_out_dir/crashpad_test_data/{{source}}" ] + } + + deps += [ ":crashpad_test_data" ] + + extra_dist_files = [ + "$root_out_dir/crashpad_handler", + "$root_out_dir/crashpad_test_test_multiprocess_exec_test_child", + "$root_out_dir/crashpad_test_data", + ] + } } if (crashpad_is_in_fuchsia) { import("//build/package.gni") package("crashpad_test") { testonly = true - deprecated_system_image = true deps = [ ":crashpad_tests", @@ -45,7 +87,6 @@ if (crashpad_is_in_chromium || crashpad_is_in_fuchsia) { "snapshot:crashpad_snapshot_test_module_large", "snapshot:crashpad_snapshot_test_module_small", "test:crashpad_test_test_multiprocess_exec_test_child", - "util:generate_test_server_key", "util:http_transport_test_server", ] @@ -53,24 +94,23 @@ if (crashpad_is_in_chromium || crashpad_is_in_fuchsia) { { name = "crashpad_tests" }, + ] + + meta = [ + { + path = "test/fuchsia_crashpad_tests.cmx" + dest = "crashpad_tests.cmx" + }, + ] + + binaries = [ { name = "crashpad_test_test_multiprocess_exec_test_child" - dest = "crashpad_test_data/crashpad_test_test_multiprocess_exec_test_child" + dest = "crashpad_test_test_multiprocess_exec_test_child" }, { name = "http_transport_test_server" - dest = "crashpad_test_data/http_transport_test_server" - }, - - # These aren't actually tests, but that seems to be the only way to - # convince package() to get them from the output directory. - { - name = "crashpad_util_test_cert.pem" - dest = "crashpad_test_data/crashpad_util_test_cert.pem" - }, - { - name = "crashpad_util_test_key.pem" - dest = "crashpad_test_data/crashpad_util_test_key.pem" + dest = "http_transport_test_server" }, ] @@ -92,43 +132,39 @@ if (crashpad_is_in_chromium || crashpad_is_in_fuchsia) { resources = [ { path = "util/net/testdata/ascii_http_body.txt" - dest = "crashpad_test_data/util/net/testdata/ascii_http_body.txt" + dest = "util/net/testdata/ascii_http_body.txt" }, { path = "util/net/testdata/binary_http_body.dat" - dest = "crashpad_test_data/util/net/testdata/binary_http_body.dat" + dest = "util/net/testdata/binary_http_body.dat" }, { path = "test/test_paths_test_data_root.txt" - dest = "crashpad_test_data/test/test_paths_test_data_root.txt" + dest = "test/test_paths_test_data_root.txt" }, ] - } - package("crashpad_handler") { - deprecated_system_image = true - - deps = [ - "handler:crashpad_handler", - ] - - binaries = [ - { - name = "crashpad_handler" - }, - ] + if (crashpad_use_boringssl_for_http_transport_socket) { + resources += [ + { + path = "util/net/testdata/crashpad_util_test_cert.pem" + dest = "util/net/testdata/crashpad_util_test_cert.pem" + }, + { + path = "util/net/testdata/crashpad_util_test_key.pem" + dest = "util/net/testdata/crashpad_util_test_key.pem" + }, + ] + } } package("crashpad_database_util") { - deprecated_system_image = true - - deps = [ - "tools:crashpad_database_util", - ] + deps = [ "tools:crashpad_database_util" ] binaries = [ { name = "crashpad_database_util" + shell = true }, ] } @@ -146,6 +182,9 @@ if (crashpad_is_in_chromium || crashpad_is_in_fuchsia) { "handler:handler_test", "test:gtest_main", ] + if (crashpad_is_ios || crashpad_is_fuchsia) { + deps -= [ "handler:handler_test" ] + } } test("crashpad_minidump_test") { @@ -160,6 +199,9 @@ if (crashpad_is_in_chromium || crashpad_is_in_fuchsia) { "snapshot:snapshot_test", "test:gtest_main", ] + if (crashpad_is_ios) { + deps -= [ "snapshot:snapshot_test" ] + } } test("crashpad_test_test") { @@ -176,3 +218,10 @@ if (crashpad_is_in_chromium || crashpad_is_in_fuchsia) { ] } } + +if (crashpad_is_ios) { + group("ios_xcuitests") { + testonly = true + deps = [ "test/ios:all_tests" ] + } +} diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 2080cc45..8724b7f3 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -12,3 +12,4 @@ Mark Mentovai Robert Sesek Scott Graham +Joshua Peraza diff --git a/DEPS b/DEPS index 34f1ef68..81b9583b 100644 --- a/DEPS +++ b/DEPS @@ -15,22 +15,34 @@ vars = { 'chromium_git': 'https://chromium.googlesource.com', 'pull_linux_clang': False, - 'pull_win_toolchain': False + 'pull_win_toolchain': False, + # Controls whether crashpad/build/ios/setup-ios-gn.py is run as part of + # gclient hooks. It is enabled by default for developer's convenience. It can + # be disabled with custom_vars (done automatically on the bots). + 'run_setup_ios_gn': True, } deps = { 'buildtools': - Var('chromium_git') + '/chromium/buildtools.git@' + - '6fe4a3251488f7af86d64fc25cf442e817cf6133', + Var('chromium_git') + '/chromium/src/buildtools.git@' + + '4164a305626786b1912d467003acf4c4995bec7d', + 'crashpad/third_party/edo/edo': { + 'url': Var('chromium_git') + '/external/github.com/google/eDistantObject.git@' + + '243fc89ae95b24717d41f3786f6a9abeeef87c92', + 'condition': 'checkout_ios', + }, 'crashpad/third_party/gtest/gtest': Var('chromium_git') + '/external/github.com/google/googletest@' + - 'c091b0469ab4c04ee9411ef770f32360945f4c53', + 'e3f0319d89f4cbf32993de595d984183b1a9fc57', 'crashpad/third_party/gyp/gyp': Var('chromium_git') + '/external/gyp@' + - '5e2b3ddde7cda5eb6bc09a5546a76b00e49d888f', + '8bee09f4a57807136593ddc906b0b213c21f9014', + 'crashpad/third_party/lss/lss': + Var('chromium_git') + '/linux-syscall-support.git@' + + '7bde79cc274d06451bf65ae82c012a5d3e476b5a', 'crashpad/third_party/mini_chromium/mini_chromium': Var('chromium_git') + '/chromium/mini_chromium@' + - '793e94e2c652831af2d25bb5288b04e59048c62d', + '8ca5ea356cdb97913d62d379d503567a80d90726', 'crashpad/third_party/libfuzzer/src': Var('chromium_git') + '/chromium/llvm-project/compiler-rt/lib/fuzzer.git@' + 'fda403cf93ecb8792cb1d061564d89a6553ca020', @@ -49,7 +61,7 @@ deps = { 'condition': 'checkout_linux and pull_linux_clang', 'dep_type': 'cipd' }, - 'crashpad/third_party/linux/clang/mac-amd64': { + 'crashpad/third_party/fuchsia/clang/mac-amd64': { 'packages': [ { 'package': 'fuchsia/clang/mac-amd64', @@ -89,22 +101,24 @@ deps = { 'condition': 'checkout_fuchsia and host_os == "linux"', 'dep_type': 'cipd' }, - 'crashpad/third_party/fuchsia/sdk/linux-amd64': { - # The SDK is keyed to the host system because it contains build tools. - # Currently, linux-amd64 is the only SDK published (see - # https://chrome-infra-packages.appspot.com/#/?path=fuchsia/sdk). - # As long as this is the case, use that SDK package - # even on other build hosts. - # The sysroot (containing headers and libraries) and other components are - # related to the target and should be functional with an appropriate - # toolchain that runs on the build host (fuchsia_clang, above). + 'crashpad/third_party/fuchsia/sdk/mac-amd64': { 'packages': [ { - 'package': 'fuchsia/sdk/linux-amd64', + 'package': 'fuchsia/sdk/gn/mac-amd64', 'version': 'latest' }, ], - 'condition': 'checkout_fuchsia', + 'condition': 'checkout_fuchsia and host_os == "mac"', + 'dep_type': 'cipd' + }, + 'crashpad/third_party/fuchsia/sdk/linux-amd64': { + 'packages': [ + { + 'package': 'fuchsia/sdk/gn/linux-amd64', + 'version': 'latest' + }, + ], + 'condition': 'checkout_fuchsia and host_os == "linux"', 'dep_type': 'cipd' }, 'crashpad/third_party/win/toolchain': { @@ -163,45 +177,6 @@ hooks = [ 'buildtools/win/clang-format.exe.sha1', ], }, - { - 'name': 'gn_mac', - 'pattern': '.', - 'condition': 'host_os == "mac"', - 'action': [ - 'download_from_google_storage', - '--no_resume', - '--no_auth', - '--bucket=chromium-gn', - '--sha1_file', - 'buildtools/mac/gn.sha1', - ], - }, - { - 'name': 'gn_linux', - 'pattern': '.', - 'condition': 'host_os == "linux"', - 'action': [ - 'download_from_google_storage', - '--no_resume', - '--no_auth', - '--bucket=chromium-gn', - '--sha1_file', - 'buildtools/linux64/gn.sha1', - ], - }, - { - 'name': 'gn_win', - 'pattern': '.', - 'condition': 'host_os == "win"', - 'action': [ - 'download_from_google_storage', - '--no_resume', - '--no_auth', - '--bucket=chromium-gn', - '--sha1_file', - 'buildtools/win/gn.exe.sha1', - ], - }, { # If using a local clang ("pull_linux_clang" above), also pull down a # sysroot. @@ -213,9 +188,13 @@ hooks = [ ], }, { - 'name': 'gyp', - 'pattern': '\.gypi?$', - 'action': ['python', 'crashpad/build/gyp_crashpad.py'], + 'name': 'setup_gn_ios', + 'pattern': '.', + 'condition': 'run_setup_ios_gn and checkout_ios', + 'action': [ + 'python', + 'crashpad/build/ios/setup_ios_gn.py' + ], }, ] diff --git a/README.md b/README.md index d74c1bd5..b20777bb 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ https://chromium.googlesource.com/crashpad/crashpad. * Bugs can be reported at the [Crashpad issue tracker](https://crashpad.chromium.org/bug/). - * The [Crashpad Buildbots](https://build.chromium.org/p/client.crashpad) + * The [Crashpad bots](https://ci.chromium.org/p/crashpad/g/main/console) perform automated builds and tests. * [crashpad-dev](https://groups.google.com/a/chromium.org/group/crashpad-dev) is the Crashpad developers’ mailing list. diff --git a/build/BUILD.gn b/build/BUILD.gn index 0ef1127f..6e6ccd64 100644 --- a/build/BUILD.gn +++ b/build/BUILD.gn @@ -32,9 +32,7 @@ config("crashpad_is_in_fuchsia") { group("default_exe_manifest_win") { if (crashpad_is_in_chromium) { - deps = [ - "//build/win:default_exe_manifest", - ] + deps = [ "//build/win:default_exe_manifest" ] } } @@ -45,7 +43,26 @@ config("crashpad_fuzzer_flags") { "-fsanitize=fuzzer", ] - ldflags = [ - "-fsanitize=address", - ] + ldflags = [ "-fsanitize=address" ] +} + +if (crashpad_is_ios) { + group("ios_enable_arc") { + if (crashpad_is_in_chromium) { + public_configs = [ "//build/config/compiler:enable_arc" ] + } else if (crashpad_is_standalone) { + public_configs = + [ "//third_party/mini_chromium/mini_chromium/build:ios_enable_arc" ] + } + } + + group("ios_xctest") { + if (crashpad_is_in_chromium) { + public_configs = [ "//build/config/ios:xctest_config" ] + } else if (crashpad_is_standalone) { + public_configs = [ + "//third_party/mini_chromium/mini_chromium/build/ios:xctest_config", + ] + } + } } diff --git a/build/BUILDCONFIG.gn b/build/BUILDCONFIG.gn index 95dc2771..bc531083 100644 --- a/build/BUILDCONFIG.gn +++ b/build/BUILDCONFIG.gn @@ -55,6 +55,7 @@ declare_args() { _default_configs = [ "//third_party/mini_chromium/mini_chromium/build:default", "//third_party/mini_chromium/mini_chromium/build:Wexit_time_destructors", + "//third_party/mini_chromium/mini_chromium/build:Wimplicit_fallthrough", ] if (crashpad_use_libfuzzer) { diff --git a/build/crashpad.gypi b/build/crashpad.gypi index 166e768f..2accb5a0 100644 --- a/build/crashpad.gypi +++ b/build/crashpad.gypi @@ -35,6 +35,11 @@ }], ], }], + ['OS=="android"', { + 'ldflags': [ + '-static-libstdc++', + ], + }], ], }, } diff --git a/build/crashpad_buildconfig.gni b/build/crashpad_buildconfig.gni index f767027f..7540fb28 100644 --- a/build/crashpad_buildconfig.gni +++ b/build/crashpad_buildconfig.gni @@ -15,28 +15,28 @@ declare_args() { # Determines various flavors of build configuration, and which concrete # targets to use for dependencies. Valid values are "standalone", "chromium", - # and "fuchsia". + # "fuchsia", "dart" or "external". crashpad_dependencies = "standalone" if (defined(is_fuchsia_tree) && is_fuchsia_tree) { - # Determines various flavors of build configuration, and which concrete - # targets to use for dependencies. Valid values are "standalone", - # "chromium", and "fuchsia". Defaulted to "fuchsia" because - # "is_fuchsia_tree" is set. crashpad_dependencies = "fuchsia" } } assert( crashpad_dependencies == "chromium" || crashpad_dependencies == "fuchsia" || - crashpad_dependencies == "standalone") + crashpad_dependencies == "standalone" || + crashpad_dependencies == "external" || crashpad_dependencies == "dart") crashpad_is_in_chromium = crashpad_dependencies == "chromium" crashpad_is_in_fuchsia = crashpad_dependencies == "fuchsia" +crashpad_is_in_dart = crashpad_dependencies == "dart" +crashpad_is_external = crashpad_dependencies == "external" crashpad_is_standalone = crashpad_dependencies == "standalone" if (crashpad_is_in_chromium) { crashpad_is_mac = is_mac + crashpad_is_ios = is_ios crashpad_is_win = is_win crashpad_is_linux = is_linux crashpad_is_android = is_android @@ -46,11 +46,18 @@ if (crashpad_is_in_chromium) { crashpad_is_clang = is_clang } else { - # Both standalone and in Fuchsia tree use mini_chromium, and it's mapped into - # the same location in both cases. - import("../third_party/mini_chromium/mini_chromium/build/compiler.gni") - import("../third_party/mini_chromium/mini_chromium/build/platform.gni") + # External and Dart SDK builds assume crashpad and mini_chromium are peers. + if (crashpad_is_external || crashpad_is_in_dart) { + import("../../../mini_chromium/mini_chromium/build/compiler.gni") + import("../../../mini_chromium/mini_chromium/build/platform.gni") + } else { + # Both standalone and in Fuchsia tree use mini_chromium, and it's mapped into + # the same location in both cases. + import("../third_party/mini_chromium/mini_chromium/build/compiler.gni") + import("../third_party/mini_chromium/mini_chromium/build/platform.gni") + } crashpad_is_mac = mini_chromium_is_mac + crashpad_is_ios = mini_chromium_is_ios crashpad_is_win = mini_chromium_is_win crashpad_is_linux = mini_chromium_is_linux crashpad_is_android = mini_chromium_is_android @@ -63,7 +70,12 @@ if (crashpad_is_in_chromium) { template("crashpad_executable") { executable(target_name) { - forward_variables_from(invoker, "*", [ "configs", "remove_configs" ]) + forward_variables_from(invoker, + "*", + [ + "configs", + "remove_configs", + ]) if (defined(invoker.remove_configs)) { configs -= invoker.remove_configs } @@ -72,21 +84,23 @@ template("crashpad_executable") { configs += invoker.configs } - if (!defined(deps)) { - deps = [] - } - - if (crashpad_is_in_chromium) { - deps += [ "//build/config:exe_and_shlib_deps" ] - } else if (crashpad_is_in_fuchsia) { - configs += [ "//build/config/fuchsia:fdio_config" ] + if (crashpad_is_in_fuchsia) { + fdio_config = [ "//build/config/fuchsia:fdio_config" ] + if (configs + fdio_config - fdio_config == configs) { + configs += fdio_config + } } } } template("crashpad_loadable_module") { loadable_module(target_name) { - forward_variables_from(invoker, "*", [ "configs", "remove_configs" ]) + forward_variables_from(invoker, + "*", + [ + "configs", + "remove_configs", + ]) if (defined(invoker.remove_configs)) { configs -= invoker.remove_configs } @@ -95,13 +109,7 @@ template("crashpad_loadable_module") { configs += invoker.configs } - if (!defined(deps)) { - deps = [] - } - - if (crashpad_is_in_chromium) { - deps += [ "//build/config:exe_and_shlib_deps" ] - } else if (crashpad_is_in_fuchsia) { + if (crashpad_is_in_fuchsia) { configs += [ "//build/config/fuchsia:fdio_config" ] } } diff --git a/build/crashpad_dependencies.gni b/build/crashpad_dependencies.gni deleted file mode 100644 index 1964facd..00000000 --- a/build/crashpad_dependencies.gni +++ /dev/null @@ -1,53 +0,0 @@ -# Copyright 2017 The Crashpad Authors. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -declare_args() { - # Determines various flavors of build configuration, and which concrete - # targets to use for dependencies. Valid values are "standalone", "chromium", - # and "fuchsia". - crashpad_dependencies = "standalone" -} - -assert( - crashpad_dependencies == "chromium" || crashpad_dependencies == "fuchsia" || - crashpad_dependencies == "standalone") - -crashpad_is_in_chromium = crashpad_dependencies == "chromium" -crashpad_is_in_fuchsia = crashpad_dependencies == "fuchsia" -crashpad_is_standalone = crashpad_dependencies == "standalone" - -if (crashpad_is_in_chromium) { - crashpad_is_posix = is_posix -} else if (crashpad_is_in_fuchsia) { - import("//third_party/mini_chromium/build/is_posix.gni") - crashpad_is_posix = mini_chromium_is_posix -} else if (crashpad_is_standalone) { - import("../third_party/mini_chromium/mini_chromium/build/is_posix.gni") - crashpad_is_posix = mini_chromium_is_posix -} - -if (crashpad_is_in_chromium) { - import("//testing/test.gni") -} else { - template("test") { - executable(target_name) { - testonly = true - forward_variables_from(invoker, "*") - } - } - - set_defaults("test") { - configs = default_executable_configs - } -} diff --git a/build/crashpad_fuzzer_test.gni b/build/crashpad_fuzzer_test.gni index 8e0eaa9f..cc709b0d 100644 --- a/build/crashpad_fuzzer_test.gni +++ b/build/crashpad_fuzzer_test.gni @@ -14,8 +14,11 @@ import("crashpad_buildconfig.gni") import("test.gni") +if (crashpad_is_in_chromium) { + import("//testing/libfuzzer/fuzzer_test.gni") +} -template("fuzzer_test") { +template("crashpad_fuzzer_test") { if (crashpad_is_standalone && crashpad_use_libfuzzer) { test(target_name) { forward_variables_from(invoker, @@ -38,6 +41,15 @@ template("fuzzer_test") { } cflags += [ "-fsanitize=fuzzer" ] } + if (defined(invoker.seed_corpus)) { + not_needed(invoker, [ "seed_corpus" ]) + } + } else if (crashpad_is_in_chromium && use_fuzzing_engine) { + # Append "crashpad_" to the beginning of the fuzzer's name to make it easier + # in Chromium to recognize where fuzzer came from. + forward_variables_from(invoker, "*") + fuzzer_test("crashpad_" + target_name) { + } } else { not_needed(invoker, "*") group(target_name) { diff --git a/build/gyp_crashpad.py b/build/gyp_crashpad.py index 84856606..41ab4b94 100755 --- a/build/gyp_crashpad.py +++ b/build/gyp_crashpad.py @@ -19,83 +19,84 @@ import sys def ChooseDependencyPath(local_path, external_path): - """Chooses between a dependency located at local path and an external path. + """Chooses between a dependency located at local path and an external path. - The local path, used in standalone builds, is preferred. If it is not present - but the external path is, the external path will be used. If neither path is - present, the local path will be used, so that error messages uniformly refer - to the local path. + The local path, used in standalone builds, is preferred. If it is not + present but the external path is, the external path will be used. If neither + path is present, the local path will be used, so that error messages + uniformly refer to the local path. - Args: - local_path: The preferred local path to use for a standalone build. - external_path: The external path to fall back to. + Args: + local_path: The preferred local path to use for a standalone build. + external_path: The external path to fall back to. - Returns: - A 2-tuple. The first element is None or 'external', depending on whether - local_path or external_path was chosen. The second element is the chosen - path. - """ - if os.path.exists(local_path) or not os.path.exists(external_path): - return (None, local_path) - return ('external', external_path) + Returns: + A 2-tuple. The first element is None or 'external', depending on whether + local_path or external_path was chosen. The second element is the chosen + path. + """ + if os.path.exists(local_path) or not os.path.exists(external_path): + return (None, local_path) + return ('external', external_path) script_dir = os.path.dirname(__file__) -crashpad_dir = (os.path.dirname(script_dir) if script_dir not in ('', os.curdir) - else os.pardir) +crashpad_dir = (os.path.dirname(script_dir) + if script_dir not in ('', os.curdir) else os.pardir) -sys.path.insert(0, - ChooseDependencyPath(os.path.join(crashpad_dir, 'third_party', 'gyp', 'gyp', - 'pylib'), - os.path.join(crashpad_dir, os.pardir, os.pardir, 'gyp', - 'pylib'))[1]) +sys.path.insert( + 0, + ChooseDependencyPath( + os.path.join(crashpad_dir, 'third_party', 'gyp', 'gyp', 'pylib'), + os.path.join(crashpad_dir, os.pardir, os.pardir, 'gyp', 'pylib'))[1]) import gyp def main(args): - if 'GYP_GENERATORS' not in os.environ: - os.environ['GYP_GENERATORS'] = 'ninja' + if 'GYP_GENERATORS' not in os.environ: + os.environ['GYP_GENERATORS'] = 'ninja' - crashpad_dir_or_dot = crashpad_dir if crashpad_dir is not '' else os.curdir + crashpad_dir_or_dot = crashpad_dir if crashpad_dir is not '' else os.curdir - (dependencies, mini_chromium_common_gypi) = (ChooseDependencyPath( - os.path.join(crashpad_dir, 'third_party', 'mini_chromium', - 'mini_chromium', 'build', 'common.gypi'), - os.path.join(crashpad_dir, os.pardir, os.pardir, 'mini_chromium', - 'mini_chromium', 'build', 'common.gypi'))) - if dependencies is not None: - args.extend(['-D', 'crashpad_dependencies=%s' % dependencies]) - args.extend(['--include', mini_chromium_common_gypi]) - args.extend(['--depth', crashpad_dir_or_dot]) - args.append(os.path.join(crashpad_dir, 'crashpad.gyp')) + (dependencies, mini_chromium_common_gypi) = (ChooseDependencyPath( + os.path.join(crashpad_dir, 'third_party', 'mini_chromium', + 'mini_chromium', 'build', 'common.gypi'), + os.path.join(crashpad_dir, os.pardir, os.pardir, 'mini_chromium', + 'mini_chromium', 'build', 'common.gypi'))) + if dependencies is not None: + args.extend(['-D', 'crashpad_dependencies=%s' % dependencies]) + args.extend(['--include', mini_chromium_common_gypi]) + args.extend(['--depth', crashpad_dir_or_dot]) + args.append(os.path.join(crashpad_dir, 'crashpad.gyp')) - result = gyp.main(args) - if result != 0: - return result - - if sys.platform == 'win32': - # Check to make sure that no target_arch was specified. target_arch may be - # set during a cross build, such as a cross build for Android. - has_target_arch = False - for arg_index in range(0, len(args)): - arg = args[arg_index] - if (arg.startswith('-Dtarget_arch=') or - (arg == '-D' and arg_index + 1 < len(args) and - args[arg_index + 1].startswith('target_arch='))): - has_target_arch = True - break - - if not has_target_arch: - # Also generate the x86 build. - result = gyp.main(args + ['-D', 'target_arch=ia32', '-G', 'config=Debug']) - if result != 0: + result = gyp.main(args) + if result != 0: return result - result = gyp.main( - args + ['-D', 'target_arch=ia32', '-G', 'config=Release']) - return result + if sys.platform == 'win32': + # Check to make sure that no target_arch was specified. target_arch may + # be set during a cross build, such as a cross build for Android. + has_target_arch = False + for arg_index in range(0, len(args)): + arg = args[arg_index] + if (arg.startswith('-Dtarget_arch=') or + (arg == '-D' and arg_index + 1 < len(args) and + args[arg_index + 1].startswith('target_arch='))): + has_target_arch = True + break + + if not has_target_arch: + # Also generate the x86 build. + result = gyp.main(args + + ['-D', 'target_arch=ia32', '-G', 'config=Debug']) + if result != 0: + return result + result = gyp.main( + args + ['-D', 'target_arch=ia32', '-G', 'config=Release']) + + return result if __name__ == '__main__': - sys.exit(main(sys.argv[1:])) + sys.exit(main(sys.argv[1:])) diff --git a/build/gyp_crashpad_android.py b/build/gyp_crashpad_android.py index f78c05b9..327fa21f 100755 --- a/build/gyp_crashpad_android.py +++ b/build/gyp_crashpad_android.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding: utf-8 # Copyright 2017 The Crashpad Authors. All rights reserved. # @@ -25,89 +24,46 @@ import sys def main(args): - parser = argparse.ArgumentParser( - description='Set up an Android cross build', - epilog='Additional arguments will be passed to gyp_crashpad.py.') - parser.add_argument('--ndk', required=True, help='Standalone NDK toolchain') - parser.add_argument('--compiler', - default='clang', - choices=('clang', 'gcc'), - help='The compiler to use, clang by default') - (parsed, extra_command_line_args) = parser.parse_known_args(args) + parser = argparse.ArgumentParser( + description='Set up an Android cross build', + epilog='Additional arguments will be passed to gyp_crashpad.py.') + parser.add_argument('--arch', required=True, help='Target architecture') + parser.add_argument('--api-level', required=True, help='Target API level') + parser.add_argument('--ndk', required=True, help='Standalone NDK toolchain') + (parsed, extra_command_line_args) = parser.parse_known_args(args) - NDK_ERROR=( - 'NDK must be a valid standalone NDK toolchain.\n' + - 'See https://developer.android.com/ndk/guides/standalone_toolchain.html') - arch_dirs = glob.glob(os.path.join(parsed.ndk, '*-linux-android*')) - if len(arch_dirs) != 1: - parser.error(NDK_ERROR) + ndk_bin_dir = os.path.join(parsed.ndk, 'toolchains', 'llvm', 'prebuilt', + 'linux-x86_64', 'bin') + if not os.path.exists(ndk_bin_dir): + parser.error("missing toolchain") - arch_triplet = os.path.basename(arch_dirs[0]) - ARCH_TRIPLET_TO_ARCH = { - 'arm-linux-androideabi': 'arm', - 'aarch64-linux-android': 'arm64', - 'i686-linux-android': 'ia32', - 'x86_64-linux-android': 'x64', - 'mipsel-linux-android': 'mips', - 'mips64el-linux-android': 'mips64', - } - if arch_triplet not in ARCH_TRIPLET_TO_ARCH: - parser.error(NDK_ERROR) - arch = ARCH_TRIPLET_TO_ARCH[arch_triplet] + ARCH_TO_ARCH_TRIPLET = { + 'arm': 'armv7a-linux-androideabi', + 'arm64': 'aarch64-linux-android', + 'ia32': 'i686-linux-android', + 'x64': 'x86_64-linux-android', + } - ndk_bin_dir = os.path.join(parsed.ndk, 'bin') - - clang_path = os.path.join(ndk_bin_dir, 'clang') - extra_args = [] - - if parsed.compiler == 'clang': - os.environ['CC_target'] = clang_path - os.environ['CXX_target'] = os.path.join(ndk_bin_dir, 'clang++') - elif parsed.compiler == 'gcc': - os.environ['CC_target'] = os.path.join(ndk_bin_dir, - '%s-gcc' % arch_triplet) + clang_prefix = ARCH_TO_ARCH_TRIPLET[parsed.arch] + parsed.api_level + os.environ['CC_target'] = os.path.join(ndk_bin_dir, clang_prefix + '-clang') os.environ['CXX_target'] = os.path.join(ndk_bin_dir, - '%s-g++' % arch_triplet) + clang_prefix + '-clang++') - # Unlike the Clang build, when using GCC with unified headers, __ANDROID_API__ - # isn’t set automatically and must be pushed in to the build. Fish the correct - # value out of the Clang wrapper script. If deprecated headers are in use, the - # Clang wrapper won’t mention __ANDROID_API__, but the standalone toolchain’s - # will #define it for both Clang and GCC. - # - # android_api_level is extracted in this manner even when compiling with Clang - # so that it’s available for use in GYP conditions that need to test the API - # level, but beware that it’ll only be available when unified headers are in - # use. - # - # Unified headers are the way of the future, according to - # https://android.googlesource.com/platform/ndk/+/ndk-r14/CHANGELOG.md and - # https://android.googlesource.com/platform/ndk/+/master/docs/UnifiedHeaders.md. - # Traditional (deprecated) headers have been removed entirely as of NDK r16. - # https://android.googlesource.com/platform/ndk/+/ndk-release-r16/CHANGELOG.md. - with open(clang_path, 'r') as file: - clang_script_contents = file.read() - matches = re.finditer(r'\s-D__ANDROID_API__=([\d]+)\s', - clang_script_contents) - match = next(matches, None) - if match: - android_api = int(match.group(1)) - extra_args.extend(['-D', 'android_api_level=%d' % android_api]) - if next(matches, None): - raise AssertionError('__ANDROID_API__ defined too many times') + extra_args = ['-D', 'android_api_level=' + parsed.api_level] - for tool in ('ar', 'nm', 'readelf'): - os.environ['%s_target' % tool.upper()] = ( - os.path.join(ndk_bin_dir, '%s-%s' % (arch_triplet, tool))) + # ARM only includes 'v7a' in the tool prefix for clang + tool_prefix = ('arm-linux-androideabi' if parsed.arch == 'arm' else + ARCH_TO_ARCH_TRIPLET[parsed.arch]) - return gyp_crashpad.main( - ['-D', 'OS=android', - '-D', 'target_arch=%s' % arch, - '-D', 'clang=%d' % (1 if parsed.compiler == 'clang' else 0), - '-f', 'ninja-android'] + - extra_args + - extra_command_line_args) + for tool in ('ar', 'nm', 'readelf'): + os.environ['%s_target' % tool.upper()] = (os.path.join( + ndk_bin_dir, '%s-%s' % (tool_prefix, tool))) + + return gyp_crashpad.main([ + '-D', 'OS=android', '-D', + 'target_arch=%s' % parsed.arch, '-D', 'clang=1', '-f', 'ninja-android' + ] + extra_args + extra_command_line_args) if __name__ == '__main__': - sys.exit(main(sys.argv[1:])) + sys.exit(main(sys.argv[1:])) diff --git a/build/install_linux_sysroot.py b/build/install_linux_sysroot.py index afa88157..97f2c140 100755 --- a/build/install_linux_sysroot.py +++ b/build/install_linux_sysroot.py @@ -23,7 +23,6 @@ import subprocess import sys import urllib2 - SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) # Sysroot revision from: @@ -33,42 +32,43 @@ 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) + url = '%s/%s/%s/%s' % (SERVER, PATH, REVISION, FILENAME) - sysroot = os.path.join(SCRIPT_DIR, os.pardir, - 'third_party', 'linux', 'sysroot') + sysroot = os.path.join(SCRIPT_DIR, os.pardir, 'third_party', 'linux', + 'sysroot') - stamp = os.path.join(sysroot, '.stamp') - if os.path.exists(stamp): - with open(stamp) as s: - if s.read() == url: - return + stamp = os.path.join(sysroot, '.stamp') + if os.path.exists(stamp): + with open(stamp) as s: + if s.read() == url: + return - print 'Installing Debian root image from %s' % url + print 'Installing Debian root image from %s' % url - if os.path.isdir(sysroot): - shutil.rmtree(sysroot) - os.mkdir(sysroot) - tarball = os.path.join(sysroot, FILENAME) - print 'Downloading %s' % url + if os.path.isdir(sysroot): + shutil.rmtree(sysroot) + os.mkdir(sysroot) + tarball = os.path.join(sysroot, FILENAME) + print 'Downloading %s' % url - for _ in range(3): - response = urllib2.urlopen(url) - with open(tarball, 'wb') as f: - f.write(response.read()) - break - else: - raise Exception('Failed to download %s' % url) + for _ in range(3): + response = urllib2.urlopen(url) + with open(tarball, 'wb') as f: + f.write(response.read()) + break + else: + raise Exception('Failed to download %s' % url) - subprocess.check_call(['tar', 'xf', tarball, '-C', sysroot]) + subprocess.check_call(['tar', 'xf', tarball, '-C', sysroot]) - os.remove(tarball) + os.remove(tarball) - with open(stamp, 'w') as s: - s.write(url) + with open(stamp, 'w') as s: + s.write(url) if __name__ == '__main__': - main() - sys.exit(0) + main() + sys.exit(0) diff --git a/build/ios/Default.png b/build/ios/Default.png new file mode 100644 index 00000000..8c9089d5 Binary files /dev/null and b/build/ios/Default.png differ diff --git a/build/ios/Unittest-Info.plist b/build/ios/Unittest-Info.plist new file mode 100644 index 00000000..9256fb44 --- /dev/null +++ b/build/ios/Unittest-Info.plist @@ -0,0 +1,10 @@ + + + + + CFBundleIdentifier + ${IOS_BUNDLE_ID_PREFIX}.gtest.${GTEST_BUNDLE_ID_SUFFIX:rfc1034identifier} + UIApplicationDelegate + CrashpadUnitTestDelegate + + diff --git a/build/ios/convert_gn_xcodeproj.py b/build/ios/convert_gn_xcodeproj.py new file mode 100755 index 00000000..c00d3318 --- /dev/null +++ b/build/ios/convert_gn_xcodeproj.py @@ -0,0 +1,283 @@ +#!/usr/bin/env python + +# Copyright 2020 The Crashpad Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Convert GN Xcode projects to platform and configuration independent targets. + +GN generates Xcode projects that build one configuration only. However, typical +iOS development involves using the Xcode IDE to toggle the platform and +configuration. This script replaces the 'gn' configuration with 'Debug', +'Release' and 'Profile', and changes the ninja invocation to honor these +configurations. +""" + +import argparse +import collections +import copy +import filecmp +import json +import hashlib +import os +import plistlib +import random +import shutil +import subprocess +import sys +import tempfile + + +class XcodeProject(object): + + def __init__(self, objects, counter=0): + self.objects = objects + self.counter = 0 + + def AddObject(self, parent_name, obj): + while True: + self.counter += 1 + str_id = "%s %s %d" % (parent_name, obj['isa'], self.counter) + new_id = hashlib.sha1(str_id).hexdigest()[:24].upper() + + # Make sure ID is unique. It's possible there could be an id + # conflict since this is run after GN runs. + if new_id not in self.objects: + self.objects[new_id] = obj + return new_id + + +def CopyFileIfChanged(source_path, target_path): + """Copy |source_path| to |target_path| is different.""" + target_dir = os.path.dirname(target_path) + if not os.path.isdir(target_dir): + os.makedirs(target_dir) + if (not os.path.exists(target_path) or + not filecmp.cmp(source_path, target_path)): + shutil.copyfile(source_path, target_path) + + +def LoadXcodeProjectAsJSON(path): + """Return Xcode project at |path| as a JSON string.""" + return subprocess.check_output( + ['plutil', '-convert', 'json', '-o', '-', path]) + + +def WriteXcodeProject(output_path, json_string): + """Save Xcode project to |output_path| as XML.""" + with tempfile.NamedTemporaryFile() as temp_file: + temp_file.write(json_string) + temp_file.flush() + subprocess.check_call(['plutil', '-convert', 'xml1', temp_file.name]) + CopyFileIfChanged(temp_file.name, output_path) + + +def UpdateProductsProject(file_input, file_output, configurations, root_dir): + """Update Xcode project to support multiple configurations. + + Args: + file_input: path to the input Xcode project + file_output: path to the output file + configurations: list of string corresponding to the configurations that + need to be supported by the tweaked Xcode projects, must contains at + least one value. + """ + json_data = json.loads(LoadXcodeProjectAsJSON(file_input)) + project = XcodeProject(json_data['objects']) + + objects_to_remove = [] + for value in project.objects.values(): + isa = value['isa'] + + # Teach build shell script to look for the configuration and platform. + if isa == 'PBXShellScriptBuildPhase': + value['shellScript'] = value['shellScript'].replace( + 'ninja -C .', + 'ninja -C "../${CONFIGURATION}${EFFECTIVE_PLATFORM_NAME}"') + + # Add new configuration, using the first one as default. + if isa == 'XCConfigurationList': + value['defaultConfigurationName'] = configurations[0] + objects_to_remove.extend(value['buildConfigurations']) + + build_config_template = project.objects[value['buildConfigurations'] + [0]] + build_settings = build_config_template['buildSettings'] + build_settings['CONFIGURATION_BUILD_DIR'] = ( + '$(PROJECT_DIR)/../$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)') + build_settings['CODE_SIGN_IDENTITY'] = '' + + value['buildConfigurations'] = [] + for configuration in configurations: + new_build_config = copy.copy(build_config_template) + new_build_config['name'] = configuration + value['buildConfigurations'].append( + project.AddObject('products', new_build_config)) + + for object_id in objects_to_remove: + del project.objects[object_id] + + AddMarkdownToProject(project, root_dir, json_data['rootObject']) + + objects = collections.OrderedDict(sorted(project.objects.iteritems())) + WriteXcodeProject(file_output, json.dumps(json_data)) + + +def AddMarkdownToProject(project, root_dir, root_object): + list_files_cmd = ['git', '-C', root_dir, 'ls-files', '*.md'] + paths = subprocess.check_output(list_files_cmd).splitlines() + ios_internal_dir = os.path.join(root_dir, 'ios_internal') + if os.path.exists(ios_internal_dir): + list_files_cmd = ['git', '-C', ios_internal_dir, 'ls-files', '*.md'] + ios_paths = subprocess.check_output(list_files_cmd).splitlines() + paths.extend(["ios_internal/" + path for path in ios_paths]) + for path in paths: + new_markdown_entry = { + "fileEncoding": "4", + "isa": "PBXFileReference", + "lastKnownFileType": "net.daringfireball.markdown", + "name": os.path.basename(path), + "path": path, + "sourceTree": "" + } + new_markdown_entry_id = project.AddObject('sources', new_markdown_entry) + folder = GetFolderForPath(project, root_object, os.path.dirname(path)) + folder['children'].append(new_markdown_entry_id) + + +def GetFolderForPath(project, rootObject, path): + objects = project.objects + # 'Sources' is always the first child of + # project->rootObject->mainGroup->children. + root = objects[objects[objects[rootObject]['mainGroup']]['children'][0]] + if not path: + return root + for folder in path.split('/'): + children = root['children'] + new_root = None + for child in children: + if (objects[child]['isa'] == 'PBXGroup' and + objects[child]['name'] == folder): + new_root = objects[child] + break + if not new_root: + # If the folder isn't found we could just cram it into the leaf + # existing folder, but that leads to folders with tons of README.md + # inside. + new_group = { + "children": [], + "isa": "PBXGroup", + "name": folder, + "sourceTree": "" + } + new_group_id = project.AddObject('sources', new_group) + children.append(new_group_id) + new_root = objects[new_group_id] + root = new_root + return root + + +def DisableNewBuildSystem(output_dir): + """Disables the new build system due to crbug.com/852522 """ + xcwspacesharedsettings = os.path.join(output_dir, 'all.xcworkspace', + 'xcshareddata', + 'WorkspaceSettings.xcsettings') + if os.path.isfile(xcwspacesharedsettings): + json_data = json.loads(LoadXcodeProjectAsJSON(xcwspacesharedsettings)) + else: + json_data = {} + json_data['BuildSystemType'] = 'Original' + WriteXcodeProject(xcwspacesharedsettings, json.dumps(json_data)) + + +def ConvertGnXcodeProject(root_dir, input_dir, output_dir, configurations): + '''Tweak the Xcode project generated by gn to support multiple + configurations. + + The Xcode projects generated by "gn gen --ide" only supports a single + platform and configuration (as the platform and configuration are set per + output directory). This method takes as input such projects and add support + for multiple configurations and platforms (to allow devs to select them in + Xcode). + + Args: + input_dir: directory containing the XCode projects created by "gn gen + --ide" + output_dir: directory where the tweaked Xcode projects will be saved + configurations: list of string corresponding to the configurations that + need to be supported by the tweaked Xcode projects, must contains at + least one value. + ''' + # Update products project. + products = os.path.join('products.xcodeproj', 'project.pbxproj') + product_input = os.path.join(input_dir, products) + product_output = os.path.join(output_dir, products) + UpdateProductsProject(product_input, product_output, configurations, + root_dir) + + # Copy all workspace. + xcwspace = os.path.join('all.xcworkspace', 'contents.xcworkspacedata') + CopyFileIfChanged(os.path.join(input_dir, xcwspace), + os.path.join(output_dir, xcwspace)) + + # TODO(crbug.com/852522): Disable new BuildSystemType. + DisableNewBuildSystem(output_dir) + + # TODO(crbug.com/679110): gn has been modified to remove 'sources.xcodeproj' + # and keep 'all.xcworkspace' and 'products.xcodeproj'. The following code is + # here to support both old and new projects setup and will be removed once + # gn has rolled past it. + sources = os.path.join('sources.xcodeproj', 'project.pbxproj') + if os.path.isfile(os.path.join(input_dir, sources)): + CopyFileIfChanged(os.path.join(input_dir, sources), + os.path.join(output_dir, sources)) + + +def Main(args): + parser = argparse.ArgumentParser( + description='Convert GN Xcode projects for iOS.') + parser.add_argument( + 'input', help='directory containing [product|all] Xcode projects.') + parser.add_argument( + 'output', help='directory where to generate the iOS configuration.') + parser.add_argument('--add-config', + dest='configurations', + default=[], + action='append', + help='configuration to add to the Xcode project') + parser.add_argument('--root', + type=os.path.abspath, + required=True, + help='root directory of the project') + args = parser.parse_args(args) + + if not os.path.isdir(args.input): + sys.stderr.write('Input directory does not exists.\n') + return 1 + + required = set(['products.xcodeproj', 'all.xcworkspace']) + if not required.issubset(os.listdir(args.input)): + sys.stderr.write( + 'Input directory does not contain all necessary Xcode projects.\n') + return 1 + + if not args.configurations: + sys.stderr.write( + 'At least one configuration required, see --add-config.\n') + return 1 + + ConvertGnXcodeProject(args.root, args.input, args.output, + args.configurations) + + +if __name__ == '__main__': + sys.exit(Main(sys.argv[1:])) diff --git a/build/ios/setup_ios_gn.config b/build/ios/setup_ios_gn.config new file mode 100644 index 00000000..30c31115 --- /dev/null +++ b/build/ios/setup_ios_gn.config @@ -0,0 +1,39 @@ +# Copyright 2020 The Crashpad Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +[goma] +# Controls whether goma is enabled or not. If you generally use goma but +# want to disable goma for a single build, consider using the environment +# variable GOMA_DISABLED. +enabled = False +install = "$GOMA_DIR" + +[xcode] +# Controls settings for the generated Xcode project. If jobs is non-zero +# it will be passed to the ninja invocation in Xcode project. +jobs = 0 + +[build] +# Controls the build output. The only supported values are "64-bit", "32-bit" +# and "fat" (for a fat binary supporting both "32-bit" and "64-bit" cpus). +arch = "64-bit" + +[gn_args] +# Values in that section will be copied verbatim in the generated args.gn file. +target_os = "ios" + +[filters] +# List of target files to pass to --filters argument of gn gen when generating +# the Xcode project. By default, list all targets from ios/ and ios_internal/ +# and the targets corresponding to the unit tests run on the bots. diff --git a/build/ios/setup_ios_gn.py b/build/ios/setup_ios_gn.py new file mode 100755 index 00000000..934b67c6 --- /dev/null +++ b/build/ios/setup_ios_gn.py @@ -0,0 +1,350 @@ +#!/usr/bin/env python + +# Copyright 2020 The Crashpad Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse +import convert_gn_xcodeproj +import errno +import os +import re +import shutil +import subprocess +import sys +import tempfile +import ConfigParser + +try: + import cStringIO as StringIO +except ImportError: + import StringIO + +SUPPORTED_TARGETS = ('iphoneos', 'iphonesimulator') +SUPPORTED_CONFIGS = ('Debug', 'Release', 'Profile', 'Official', 'Coverage') + + +class ConfigParserWithStringInterpolation(ConfigParser.SafeConfigParser): + '''A .ini file parser that supports strings and environment variables.''' + + ENV_VAR_PATTERN = re.compile(r'\$([A-Za-z0-9_]+)') + + def values(self, section): + return map(lambda (k, v): self._UnquoteString(self._ExpandEnvVar(v)), + ConfigParser.SafeConfigParser.items(self, section)) + + def getstring(self, section, option): + return self._UnquoteString(self._ExpandEnvVar(self.get(section, + option))) + + def _UnquoteString(self, string): + if not string or string[0] != '"' or string[-1] != '"': + return string + return string[1:-1] + + def _ExpandEnvVar(self, value): + match = self.ENV_VAR_PATTERN.search(value) + if not match: + return value + name, (begin, end) = match.group(1), match.span(0) + prefix, suffix = value[:begin], self._ExpandEnvVar(value[end:]) + return prefix + os.environ.get(name, '') + suffix + + +class GnGenerator(object): + '''Holds configuration for a build and method to generate gn default + files.''' + + FAT_BUILD_DEFAULT_ARCH = '64-bit' + + TARGET_CPU_VALUES = { + 'iphoneos': { + '32-bit': '"arm"', + '64-bit': '"arm64"', + }, + 'iphonesimulator': { + '32-bit': '"x86"', + '64-bit': '"x64"', + } + } + + def __init__(self, settings, config, target): + assert target in SUPPORTED_TARGETS + assert config in SUPPORTED_CONFIGS + self._settings = settings + self._config = config + self._target = target + + def _GetGnArgs(self): + """Build the list of arguments to pass to gn. + + Returns: + A list of tuple containing gn variable names and variable values (it + is not a dictionary as the order needs to be preserved). + """ + args = [] + + args.append(('is_debug', self._config in ('Debug', 'Coverage'))) + + if os.environ.get('FORCE_MAC_TOOLCHAIN', '0') == '1': + args.append(('use_system_xcode', False)) + + cpu_values = self.TARGET_CPU_VALUES[self._target] + build_arch = self._settings.getstring('build', 'arch') + if build_arch == 'fat': + target_cpu = cpu_values[self.FAT_BUILD_DEFAULT_ARCH] + args.append(('target_cpu', target_cpu)) + args.append( + ('additional_target_cpus', + [cpu for cpu in cpu_values.itervalues() if cpu != target_cpu])) + else: + args.append(('target_cpu', cpu_values[build_arch])) + + # Add user overrides after the other configurations so that they can + # refer to them and override them. + args.extend(self._settings.items('gn_args')) + return args + + def Generate(self, gn_path, root_path, out_path): + buf = StringIO.StringIO() + self.WriteArgsGn(buf) + WriteToFileIfChanged(os.path.join(out_path, 'args.gn'), + buf.getvalue(), + overwrite=True) + + subprocess.check_call( + self.GetGnCommand(gn_path, root_path, out_path, True)) + + def CreateGnRules(self, gn_path, root_path, out_path): + buf = StringIO.StringIO() + self.WriteArgsGn(buf) + WriteToFileIfChanged(os.path.join(out_path, 'args.gn'), + buf.getvalue(), + overwrite=True) + + buf = StringIO.StringIO() + gn_command = self.GetGnCommand(gn_path, root_path, out_path, False) + self.WriteBuildNinja(buf, gn_command) + WriteToFileIfChanged(os.path.join(out_path, 'build.ninja'), + buf.getvalue(), + overwrite=False) + + buf = StringIO.StringIO() + self.WriteBuildNinjaDeps(buf) + WriteToFileIfChanged(os.path.join(out_path, 'build.ninja.d'), + buf.getvalue(), + overwrite=False) + + def WriteArgsGn(self, stream): + stream.write('# This file was generated by setup-gn.py. Do not edit\n') + stream.write('# but instead use ~/.setup-gn or $repo/.setup-gn files\n') + stream.write('# to configure settings.\n') + stream.write('\n') + + if self._settings.has_section('$imports$'): + for import_rule in self._settings.values('$imports$'): + stream.write('import("%s")\n' % import_rule) + stream.write('\n') + + gn_args = self._GetGnArgs() + for name, value in gn_args: + if isinstance(value, bool): + stream.write('%s = %s\n' % (name, str(value).lower())) + elif isinstance(value, list): + stream.write('%s = [%s' % + (name, '\n' if len(value) > 1 else '')) + if len(value) == 1: + prefix = ' ' + suffix = ' ' + else: + prefix = ' ' + suffix = ',\n' + for item in value: + if isinstance(item, bool): + stream.write('%s%s%s' % + (prefix, str(item).lower(), suffix)) + else: + stream.write('%s%s%s' % (prefix, item, suffix)) + stream.write(']\n') + else: + stream.write('%s = %s\n' % (name, value)) + + def WriteBuildNinja(self, stream, gn_command): + stream.write('rule gn\n') + stream.write(' command = %s\n' % NinjaEscapeCommand(gn_command)) + stream.write(' description = Regenerating ninja files\n') + stream.write('\n') + stream.write('build build.ninja: gn\n') + stream.write(' generator = 1\n') + stream.write(' depfile = build.ninja.d\n') + + def WriteBuildNinjaDeps(self, stream): + stream.write('build.ninja: nonexistant_file.gn\n') + + def GetGnCommand(self, gn_path, src_path, out_path, generate_xcode_project): + gn_command = [gn_path, '--root=%s' % os.path.realpath(src_path), '-q'] + if generate_xcode_project: + gn_command.append('--ide=xcode') + gn_command.append('--root-target=gn_all') + if self._settings.getboolean('goma', 'enabled'): + ninja_jobs = self._settings.getint('xcode', 'jobs') or 200 + gn_command.append('--ninja-extra-args=-j%s' % ninja_jobs) + if self._settings.has_section('filters'): + target_filters = self._settings.values('filters') + if target_filters: + gn_command.append('--filters=%s' % ';'.join(target_filters)) + # TODO(justincohen): --check is currently failing in crashpad. + # else: + # gn_command.append('--check') + gn_command.append('gen') + gn_command.append('//%s' % os.path.relpath(os.path.abspath(out_path), + os.path.abspath(src_path))) + return gn_command + + +def WriteToFileIfChanged(filename, content, overwrite): + '''Write |content| to |filename| if different. If |overwrite| is False + and the file already exists it is left untouched.''' + if os.path.exists(filename): + if not overwrite: + return + with open(filename) as file: + if file.read() == content: + return + if not os.path.isdir(os.path.dirname(filename)): + os.makedirs(os.path.dirname(filename)) + with open(filename, 'w') as file: + file.write(content) + + +def NinjaNeedEscape(arg): + '''Returns True if |arg| needs to be escaped when written to .ninja file.''' + return ':' in arg or '*' in arg or ';' in arg + + +def NinjaEscapeCommand(command): + '''Escapes |command| in order to write it to .ninja file.''' + result = [] + for arg in command: + if NinjaNeedEscape(arg): + arg = arg.replace(':', '$:') + arg = arg.replace(';', '\\;') + arg = arg.replace('*', '\\*') + else: + result.append(arg) + return ' '.join(result) + + +def FindGn(): + '''Returns absolute path to gn binary looking at the PATH env variable.''' + for path in os.environ['PATH'].split(os.path.pathsep): + gn_path = os.path.join(path, 'gn') + if os.path.isfile(gn_path) and os.access(gn_path, os.X_OK): + return gn_path + return None + + +def GenerateXcodeProject(gn_path, root_dir, out_dir, settings): + '''Convert GN generated Xcode project into multi-configuration Xcode + project.''' + + temp_path = tempfile.mkdtemp( + prefix=os.path.abspath(os.path.join(out_dir, '_temp'))) + try: + generator = GnGenerator(settings, 'Debug', 'iphonesimulator') + generator.Generate(gn_path, root_dir, temp_path) + convert_gn_xcodeproj.ConvertGnXcodeProject( + root_dir, os.path.join(temp_path), os.path.join(out_dir, 'build'), + SUPPORTED_CONFIGS) + finally: + if os.path.exists(temp_path): + shutil.rmtree(temp_path) + + +def GenerateGnBuildRules(gn_path, root_dir, out_dir, settings): + '''Generates all template configurations for gn.''' + for config in SUPPORTED_CONFIGS: + for target in SUPPORTED_TARGETS: + build_dir = os.path.join(out_dir, '%s-%s' % (config, target)) + generator = GnGenerator(settings, config, target) + generator.CreateGnRules(gn_path, root_dir, build_dir) + + +def Main(args): + default_root = os.path.normpath( + os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)) + + parser = argparse.ArgumentParser( + description='Generate build directories for use with gn.') + parser.add_argument( + 'root', + default=default_root, + nargs='?', + help='root directory where to generate multiple out configurations') + parser.add_argument('--import', + action='append', + dest='import_rules', + default=[], + help='path to file defining default gn variables') + args = parser.parse_args(args) + + # Load configuration (first global and then any user overrides). + settings = ConfigParserWithStringInterpolation() + settings.read([ + os.path.splitext(__file__)[0] + '.config', + os.path.expanduser('~/.setup-gn'), + ]) + + # Add private sections corresponding to --import argument. + if args.import_rules: + settings.add_section('$imports$') + for i, import_rule in enumerate(args.import_rules): + if not import_rule.startswith('//'): + import_rule = '//%s' % os.path.relpath( + os.path.abspath(import_rule), os.path.abspath(args.root)) + settings.set('$imports$', '$rule%d$' % i, import_rule) + + # Validate settings. + if settings.getstring('build', 'arch') not in ('64-bit', '32-bit', 'fat'): + sys.stderr.write('ERROR: invalid value for build.arch: %s\n' % + settings.getstring('build', 'arch')) + sys.exit(1) + + if settings.getboolean('goma', 'enabled'): + if settings.getint('xcode', 'jobs') < 0: + sys.stderr.write('ERROR: invalid value for xcode.jobs: %s\n' % + settings.get('xcode', 'jobs')) + sys.exit(1) + goma_install = os.path.expanduser(settings.getstring('goma', 'install')) + if not os.path.isdir(goma_install): + sys.stderr.write('WARNING: goma.install directory not found: %s\n' % + settings.get('goma', 'install')) + sys.stderr.write('WARNING: disabling goma\n') + settings.set('goma', 'enabled', 'false') + + # Find gn binary in PATH. + gn_path = FindGn() + if gn_path is None: + sys.stderr.write('ERROR: cannot find gn in PATH\n') + sys.exit(1) + + out_dir = os.path.join(args.root, 'out') + if not os.path.isdir(out_dir): + os.makedirs(out_dir) + + GenerateXcodeProject(gn_path, args.root, out_dir, settings) + GenerateGnBuildRules(gn_path, args.root, out_dir, settings) + + +if __name__ == '__main__': + sys.exit(Main(sys.argv[1:])) diff --git a/build/run_fuchsia_qemu.py b/build/run_fuchsia_qemu.py index 135b314e..aff0efd8 100755 --- a/build/run_fuchsia_qemu.py +++ b/build/run_fuchsia_qemu.py @@ -13,7 +13,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - """Helper script to [re]start or stop a helper Fuchsia QEMU instance to be used for running tests without a device. """ @@ -30,105 +29,117 @@ import tempfile import time try: - from subprocess import DEVNULL + from subprocess import DEVNULL except ImportError: - DEVNULL = open(os.devnull, 'r+b') + DEVNULL = open(os.devnull, 'r+b') CRASHPAD_ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir) def _Stop(pid_file): - if os.path.isfile(pid_file): - with open(pid_file, 'rb') as f: - pid = int(f.read().strip()) - try: - os.kill(pid, signal.SIGTERM) - except: - print('Unable to kill pid %d, continuing' % pid, file=sys.stderr) - os.unlink(pid_file) + if os.path.isfile(pid_file): + with open(pid_file, 'rb') as f: + pid = int(f.read().strip()) + try: + os.kill(pid, signal.SIGTERM) + except: + print('Unable to kill pid %d, continuing' % pid, file=sys.stderr) + os.unlink(pid_file) def _CheckForTun(): - """Check for networking. TODO(scottmg): Currently, this is Linux-specific. - """ - returncode = subprocess.call( - ['tunctl', '-b', '-u', getpass.getuser(), '-t', 'qemu'], - stdout=DEVNULL, stderr=DEVNULL) - if returncode != 0: - print('To use QEMU with networking on Linux, configure TUN/TAP. See:', - file=sys.stderr) - print(' https://fuchsia.googlesource.com/zircon/+/HEAD/docs/qemu.md#enabling-networking-under-qemu-x86_64-only', - file=sys.stderr) - return 2 - return 0 + """Check for networking. TODO(scottmg): Currently, this is Linux-specific. + """ + returncode = subprocess.call( + ['tunctl', '-b', '-u', + getpass.getuser(), '-t', 'qemu'], + stdout=DEVNULL, + stderr=DEVNULL) + if returncode != 0: + print('To use QEMU with networking on Linux, configure TUN/TAP. See:', + file=sys.stderr) + print( + ' https://fuchsia.googlesource.com/zircon/+/HEAD/docs/qemu.md#enabling-networking-under-qemu-x86_64-only', + file=sys.stderr) + return 2 + return 0 def _Start(pid_file): - tun_result = _CheckForTun() - if tun_result != 0: - return tun_result + tun_result = _CheckForTun() + if tun_result != 0: + return tun_result - arch = 'mac-amd64' if sys.platform == 'darwin' else 'linux-amd64' - fuchsia_dir = os.path.join(CRASHPAD_ROOT, 'third_party', 'fuchsia') - qemu_path = os.path.join(fuchsia_dir, 'qemu', arch, 'bin', - 'qemu-system-x86_64') - kernel_data_dir = os.path.join(fuchsia_dir, 'sdk', arch, 'target', 'x86_64') - kernel_path = os.path.join(kernel_data_dir, 'zircon.bin') - initrd_path = os.path.join(kernel_data_dir, 'bootdata.bin') + arch = 'mac-amd64' if sys.platform == 'darwin' else 'linux-amd64' + fuchsia_dir = os.path.join(CRASHPAD_ROOT, 'third_party', 'fuchsia') + qemu_path = os.path.join(fuchsia_dir, 'qemu', arch, 'bin', + 'qemu-system-x86_64') + kernel_data_dir = os.path.join(fuchsia_dir, 'sdk', arch, 'target', 'x86_64') + kernel_path = os.path.join(kernel_data_dir, 'zircon.bin') + initrd_path = os.path.join(kernel_data_dir, 'bootdata.bin') - mac_tail = ':'.join('%02x' % random.randint(0, 255) for x in range(3)) - instance_name = 'crashpad_qemu_' + \ - ''.join(chr(random.randint(ord('A'), ord('Z'))) for x in range(8)) + mac_tail = ':'.join('%02x' % random.randint(0, 255) for x in range(3)) + instance_name = ( + 'crashpad_qemu_' + + ''.join(chr(random.randint(ord('A'), ord('Z'))) for x in range(8))) - # These arguments are from the Fuchsia repo in zircon/scripts/run-zircon. - popen = subprocess.Popen([ - qemu_path, - '-m', '2048', - '-nographic', - '-kernel', kernel_path, - '-initrd', initrd_path, - '-smp', '4', - '-serial', 'stdio', - '-monitor', 'none', - '-machine', 'q35', - '-cpu', 'host,migratable=no', - '-enable-kvm', - '-netdev', 'type=tap,ifname=qemu,script=no,downscript=no,id=net0', - '-device', 'e1000,netdev=net0,mac=52:54:00:' + mac_tail, - '-append', 'TERM=dumb zircon.nodename=' + instance_name, - ], stdin=DEVNULL, stdout=DEVNULL, stderr=DEVNULL) + # These arguments are from the Fuchsia repo in zircon/scripts/run-zircon. - with open(pid_file, 'wb') as f: - f.write('%d\n' % popen.pid) + # yapf: disable + popen = subprocess.Popen([ + qemu_path, + '-m', '2048', + '-nographic', + '-kernel', kernel_path, + '-initrd', initrd_path, + '-smp', '4', + '-serial', 'stdio', + '-monitor', 'none', + '-machine', 'q35', + '-cpu', 'host,migratable=no', + '-enable-kvm', + '-netdev', 'type=tap,ifname=qemu,script=no,downscript=no,id=net0', + '-device', 'e1000,netdev=net0,mac=52:54:00:' + mac_tail, + '-append', 'TERM=dumb zircon.nodename=' + instance_name, + ], + stdin=DEVNULL, + stdout=DEVNULL, + stderr=DEVNULL) + # yapf: enable - for i in range(10): - netaddr_path = os.path.join(fuchsia_dir, 'sdk', arch, 'tools', 'netaddr') - if subprocess.call([netaddr_path, '--nowait', instance_name], - stdout=open(os.devnull), stderr=open(os.devnull)) == 0: - break - time.sleep(.5) - else: - print('instance did not respond after start', file=sys.stderr) - return 1 + with open(pid_file, 'wb') as f: + f.write('%d\n' % popen.pid) - return 0 + for i in range(10): + netaddr_path = os.path.join(fuchsia_dir, 'sdk', arch, 'tools', + 'netaddr') + if subprocess.call([netaddr_path, '--nowait', instance_name], + stdout=open(os.devnull), + stderr=open(os.devnull)) == 0: + break + time.sleep(.5) + else: + print('instance did not respond after start', file=sys.stderr) + return 1 + + return 0 def main(args): - if len(args) != 1 or args[0] not in ('start', 'stop'): - print('usage: run_fuchsia_qemu.py start|stop', file=sys.stderr) - return 1 + if len(args) != 1 or args[0] not in ('start', 'stop'): + print('usage: run_fuchsia_qemu.py start|stop', file=sys.stderr) + return 1 - command = args[0] + command = args[0] - pid_file = os.path.join(tempfile.gettempdir(), 'crashpad_fuchsia_qemu_pid') - _Stop(pid_file) - if command == 'start': - return _Start(pid_file) + pid_file = os.path.join(tempfile.gettempdir(), 'crashpad_fuchsia_qemu_pid') + _Stop(pid_file) + if command == 'start': + return _Start(pid_file) - return 0 + return 0 if __name__ == '__main__': - sys.exit(main(sys.argv[1:])) + sys.exit(main(sys.argv[1:])) diff --git a/build/run_tests.py b/build/run_tests.py index df7d8af1..2f1919b5 100755 --- a/build/run_tests.py +++ b/build/run_tests.py @@ -24,6 +24,7 @@ import posixpath import re import subprocess import sys +import tempfile import uuid CRASHPAD_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), @@ -32,511 +33,603 @@ 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.""" + """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]) + 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 + return None def _BinaryDirTargetOS(binary_dir): - """Returns the apparent target OS of binary_dir, or None if none appear to be - explicitly specified.""" + """Returns the apparent target OS of binary_dir, or None if none appear to + be explicitly specified.""" - gn_path = _FindGNFromBinaryDir(binary_dir) + gn_path = _FindGNFromBinaryDir(binary_dir) - if gn_path: - # Look for a GN “target_os”. - popen = subprocess.Popen([gn_path, '--root=' + CRASHPAD_DIR, - 'args', binary_dir, - '--list=target_os', '--short'], - shell=IS_WINDOWS_HOST, - stdout=subprocess.PIPE, stderr=open(os.devnull)) - value = popen.communicate()[0] - if popen.returncode == 0: - match = re.match('target_os = "(.*)"$', value.decode('utf-8')) - if match: - return match.group(1) + if gn_path: + # Look for a GN “target_os”. + popen = subprocess.Popen([ + gn_path, '--root=' + CRASHPAD_DIR, 'args', binary_dir, + '--list=target_os', '--short' + ], + shell=IS_WINDOWS_HOST, + stdout=subprocess.PIPE, + stderr=open(os.devnull)) + value = popen.communicate()[0] + if popen.returncode == 0: + match = re.match('target_os = "(.*)"$', value.decode('utf-8')) + if match: + return match.group(1) - # For GYP with Ninja, look for the appearance of “linux-android” in the path - # to ar. This path is configured by gyp_crashpad_android.py. - build_ninja_path = os.path.join(binary_dir, 'build.ninja') - if os.path.exists(build_ninja_path): - with open(build_ninja_path) as build_ninja_file: - build_ninja_content = build_ninja_file.read() - match = re.search('^ar = .+-linux-android(eabi)?-ar$', - build_ninja_content, - re.MULTILINE) - if match: - return 'android' + # For GYP with Ninja, look for the appearance of “linux-android” in the path + # to ar. This path is configured by gyp_crashpad_android.py. + build_ninja_path = os.path.join(binary_dir, 'build.ninja') + if os.path.exists(build_ninja_path): + with open(build_ninja_path) as build_ninja_file: + build_ninja_content = build_ninja_file.read() + match = re.search('-linux-android(eabi)?-ar$', build_ninja_content, + re.MULTILINE) + if match: + return 'android' - return None + return None def _EnableVTProcessingOnWindowsConsole(): - """Enables virtual terminal processing for ANSI/VT100-style escape sequences - on a Windows console attached to standard output. Returns True on success. - Returns False if standard output is not a console or if virtual terminal - processing is not supported. The feature was introduced in Windows 10. - """ + """Enables virtual terminal processing for ANSI/VT100-style escape sequences + on a Windows console attached to standard output. Returns True on success. + Returns False if standard output is not a console or if virtual terminal + processing is not supported. The feature was introduced in Windows 10. + """ - import pywintypes - import win32console - import winerror + import pywintypes + import win32console + import winerror - stdout_console = win32console.GetStdHandle(win32console.STD_OUTPUT_HANDLE) - try: - console_mode = stdout_console.GetConsoleMode() - except pywintypes.error as e: - if e.winerror == winerror.ERROR_INVALID_HANDLE: - # Standard output is not a console. - return False - raise + stdout_console = win32console.GetStdHandle(win32console.STD_OUTPUT_HANDLE) + try: + console_mode = stdout_console.GetConsoleMode() + except pywintypes.error as e: + if e.winerror == winerror.ERROR_INVALID_HANDLE: + # Standard output is not a console. + return False + raise - try: - # From . This would be - # win32console.ENABLE_VIRTUAL_TERMINAL_PROCESSING, but it’s too new to be - # defined there. - ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 + 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 + stdout_console.SetConsoleMode(console_mode | + ENABLE_VIRTUAL_TERMINAL_PROCESSING) + except pywintypes.error as e: + if e.winerror == winerror.ERROR_INVALID_PARAMETER: + # ANSI/VT100-style escape sequence processing isn’t supported before + # Windows 10. + return False + raise - return True + return True def _RunOnAndroidTarget(binary_dir, test, android_device, extra_command_line): - local_test_path = os.path.join(binary_dir, test) - MAYBE_UNSUPPORTED_TESTS = ( - 'crashpad_client_test', - 'crashpad_handler_test', - 'crashpad_minidump_test', - 'crashpad_snapshot_test', - ) - if not os.path.exists(local_test_path) and test in MAYBE_UNSUPPORTED_TESTS: - print('This test is not present and may not be supported, skipping') - return + local_test_path = os.path.join(binary_dir, test) + MAYBE_UNSUPPORTED_TESTS = ( + 'crashpad_client_test', + 'crashpad_handler_test', + 'crashpad_minidump_test', + 'crashpad_snapshot_test', + ) + if not os.path.exists(local_test_path) and test in MAYBE_UNSUPPORTED_TESTS: + print('This test is not present and may not be supported, skipping') + return - def _adb(*args): - # Flush all of this script’s own buffered stdout output before running adb, - # which will likely produce its own output on stdout. - sys.stdout.flush() + def _adb(*args): + # Flush all of this script’s own buffered stdout output before running + # adb, which will likely produce its own output on stdout. + sys.stdout.flush() - adb_command = ['adb', '-s', android_device] - adb_command.extend(args) - subprocess.check_call(adb_command, shell=IS_WINDOWS_HOST) + adb_command = ['adb', '-s', android_device] + adb_command.extend(args) + subprocess.check_call(adb_command, shell=IS_WINDOWS_HOST) - def _adb_push(sources, destination): - args = list(sources) - args.append(destination) - _adb('push', *args) + def _adb_push(sources, destination): + args = list(sources) + args.append(destination) + _adb('push', *args) - def _adb_shell(command_args, env={}): - # Build a command to execute via “sh -c” instead of invoking it directly. - # Here’s why: - # - # /system/bin/env isn’t normally present prior to Android 6.0 (M), where - # toybox was introduced (Android platform/manifest 9a2c01e8450b). Instead, - # set environment variables by using the shell’s internal “export” command. - # - # adbd prior to Android 7.0 (N), and the adb client prior to SDK - # platform-tools version 24, don’t know how to communicate a shell command’s - # exit status. This was added in Android platform/system/core 606835ae5c4b). - # With older adb servers and clients, adb will “exit 0” indicating success - # even if the command failed on the device. This makes - # subprocess.check_call() semantics difficult to implement directly. As a - # workaround, have the device send the command’s exit status over stdout and - # pick it back up in this function. - # - # Both workarounds are implemented by giving the device a simple script, - # which adbd will run as an “sh -c” argument. - adb_command = ['adb', '-s', android_device, 'shell'] - script_commands = [] - for k, v in env.items(): - script_commands.append('export %s=%s' % (pipes.quote(k), pipes.quote(v))) - script_commands.extend([ - ' '.join(pipes.quote(x) for x in command_args), - 'status=${?}', - 'echo "status=${status}"', - 'exit ${status}']) - adb_command.append('; '.join(script_commands)) - child = subprocess.Popen(adb_command, - shell=IS_WINDOWS_HOST, - stdin=open(os.devnull), - stdout=subprocess.PIPE) + def _adb_shell(command_args, env={}): + # Build a command to execute via “sh -c” instead of invoking it + # directly. Here’s why: + # + # /system/bin/env isn’t normally present prior to Android 6.0 (M), where + # toybox was introduced (Android platform/manifest 9a2c01e8450b). + # Instead, set environment variables by using the shell’s internal + # “export” command. + # + # adbd prior to Android 7.0 (N), and the adb client prior to SDK + # platform-tools version 24, don’t know how to communicate a shell + # command’s exit status. This was added in Android platform/system/core + # 606835ae5c4b). With older adb servers and clients, adb will “exit 0” + # indicating success even if the command failed on the device. This + # makes subprocess.check_call() semantics difficult to implement + # directly. As a workaround, have the device send the command’s exit + # status over stdout and pick it back up in this function. + # + # Both workarounds are implemented by giving the device a simple script, + # which adbd will run as an “sh -c” argument. + adb_command = ['adb', '-s', android_device, 'shell'] + script_commands = [] + for k, v in env.items(): + script_commands.append('export %s=%s' % + (pipes.quote(k), pipes.quote(v))) + script_commands.extend([ + ' '.join(pipes.quote(x) for x in command_args), 'status=${?}', + 'echo "status=${status}"', 'exit ${status}' + ]) + adb_command.append('; '.join(script_commands)) + child = subprocess.Popen(adb_command, + shell=IS_WINDOWS_HOST, + stdin=open(os.devnull), + stdout=subprocess.PIPE) - FINAL_LINE_RE = re.compile('status=(\d+)$') - final_line = None - while True: - # Use readline so that the test output appears “live” when running. - data = child.stdout.readline().decode('utf-8') - if data == '': - break - if final_line is not None: - # It wasn’t really the final line. - print(final_line, end='') + FINAL_LINE_RE = re.compile('status=(\d+)$') final_line = None - if FINAL_LINE_RE.match(data.rstrip()): - final_line = data - else: - print(data, end='') + while True: + # Use readline so that the test output appears “live” when running. + data = child.stdout.readline().decode('utf-8') + if data == '': + break + if final_line is not None: + # It wasn’t really the final line. + print(final_line, end='') + final_line = None + if FINAL_LINE_RE.match(data.rstrip()): + final_line = data + else: + print(data, end='') - if final_line is None: - # Maybe there was some stderr output after the end of stdout. Old versions - # of adb, prior to when the exit status could be communicated, smush the - # two together. - raise subprocess.CalledProcessError(-1, adb_command) - status = int(FINAL_LINE_RE.match(final_line.rstrip()).group(1)) - if status != 0: - raise subprocess.CalledProcessError(status, adb_command) + if final_line is None: + # Maybe there was some stderr output after the end of stdout. Old + # versions of adb, prior to when the exit status could be + # communicated, smush the two together. + raise subprocess.CalledProcessError(-1, adb_command) + status = int(FINAL_LINE_RE.match(final_line.rstrip()).group(1)) + if status != 0: + raise subprocess.CalledProcessError(status, adb_command) - child.wait() - if child.returncode != 0: - raise subprocess.CalledProcessError(subprocess.returncode, adb_command) + child.wait() + if child.returncode != 0: + raise subprocess.CalledProcessError(subprocess.returncode, + adb_command) - # /system/bin/mktemp isn’t normally present prior to Android 6.0 (M), where - # toybox was introduced (Android platform/manifest 9a2c01e8450b). Fake it with - # a host-generated name. This won’t retry if the name is in use, but with 122 - # bits of randomness, it should be OK. This uses “mkdir” instead of “mkdir -p” - # because the latter will not indicate failure if the directory already - # exists. - device_temp_dir = '/data/local/tmp/%s.%s' % (test, uuid.uuid4().hex) - _adb_shell(['mkdir', device_temp_dir]) + # /system/bin/mktemp isn’t normally present prior to Android 6.0 (M), where + # toybox was introduced (Android platform/manifest 9a2c01e8450b). Fake it + # with a host-generated name. This won’t retry if the name is in use, but + # with 122 bits of randomness, it should be OK. This uses “mkdir” instead of + # “mkdir -p”because the latter will not indicate failure if the directory + # already exists. + device_temp_dir = '/data/local/tmp/%s.%s' % (test, uuid.uuid4().hex) + _adb_shell(['mkdir', device_temp_dir]) - try: - # Specify test dependencies that must be pushed to the device. This could be - # determined automatically in a GN build, following the example used for - # Fuchsia. Since nothing like that exists for GYP, hard-code it for - # supported tests. - test_build_artifacts = [test] - test_data = ['test/test_paths_test_data_root.txt'] + try: + # Specify test dependencies that must be pushed to the device. This + # could be determined automatically in a GN build, following the example + # used for Fuchsia. Since nothing like that exists for GYP, hard-code it + # for supported tests. + test_build_artifacts = [test, 'crashpad_handler'] + test_data = ['test/test_paths_test_data_root.txt'] - if test == 'crashpad_test_test': - test_build_artifacts.append( - 'crashpad_test_test_multiprocess_exec_test_child') - elif test == 'crashpad_util_test': - test_data.append('util/net/testdata/') + if test == 'crashpad_test_test': + test_build_artifacts.append( + 'crashpad_test_test_multiprocess_exec_test_child') + elif test == 'crashpad_util_test': + test_data.append('util/net/testdata/') - # Establish the directory structure on the device. - device_out_dir = posixpath.join(device_temp_dir, 'out') - device_mkdirs = [device_out_dir] - for source_path in test_data: - # A trailing slash could reasonably mean to copy an entire directory, but - # will interfere with what’s needed from the path split. All parent - # directories of any source_path need to be be represented in - # device_mkdirs, but it’s important that no source_path itself wind up in - # device_mkdirs, even if source_path names a directory, because that would - # cause the “adb push” of the directory below to behave incorrectly. - if source_path.endswith(posixpath.sep): - source_path = source_path[:-1] + # Establish the directory structure on the device. + device_out_dir = posixpath.join(device_temp_dir, 'out') + device_mkdirs = [device_out_dir] + for source_path in test_data: + # A trailing slash could reasonably mean to copy an entire + # directory, but will interfere with what’s needed from the path + # split. All parent directories of any source_path need to be be + # represented in device_mkdirs, but it’s important that no + # source_path itself wind up in device_mkdirs, even if source_path + # names a directory, because that would cause the “adb push” of the + # directory below to behave incorrectly. + if source_path.endswith(posixpath.sep): + source_path = source_path[:-1] - device_source_path = posixpath.join(device_temp_dir, source_path) - device_mkdir = posixpath.split(device_source_path)[0] - if device_mkdir not in device_mkdirs: - device_mkdirs.append(device_mkdir) - adb_mkdir_command = ['mkdir', '-p'] - adb_mkdir_command.extend(device_mkdirs) - _adb_shell(adb_mkdir_command) + device_source_path = posixpath.join(device_temp_dir, source_path) + device_mkdir = posixpath.split(device_source_path)[0] + if device_mkdir not in device_mkdirs: + device_mkdirs.append(device_mkdir) + adb_mkdir_command = ['mkdir', '-p'] + adb_mkdir_command.extend(device_mkdirs) + _adb_shell(adb_mkdir_command) - # Push the test binary and any other build output to the device. - local_test_build_artifacts = [] - for artifact in test_build_artifacts: - local_test_build_artifacts.append(os.path.join(binary_dir, artifact)) - _adb_push(local_test_build_artifacts, device_out_dir) + # Push the test binary and any other build output to the device. + local_test_build_artifacts = [] + for artifact in test_build_artifacts: + local_test_build_artifacts.append(os.path.join( + binary_dir, artifact)) + _adb_push(local_test_build_artifacts, device_out_dir) - # Push test data to the device. - for source_path in test_data: - _adb_push([os.path.join(CRASHPAD_DIR, source_path)], - posixpath.join(device_temp_dir, source_path)) + # Push test data to the device. + for source_path in test_data: + _adb_push([os.path.join(CRASHPAD_DIR, source_path)], + posixpath.join(device_temp_dir, source_path)) - # Run the test on the device. Pass the test data root in the environment. - # - # Because the test will not run with its standard output attached to a - # pseudo-terminal device, gtest will not normally enable colored output, so - # mimic gtest’s own logic for deciding whether to enable color by checking - # this script’s own standard output connection. The whitelist of TERM values - # comes from gtest googletest/src/gtest.cc - # testing::internal::ShouldUseColor(). - env = {'CRASHPAD_TEST_DATA_ROOT': device_temp_dir} - gtest_color = os.environ.get('GTEST_COLOR') - if gtest_color in ('auto', None): - if (sys.stdout.isatty() and - (os.environ.get('TERM') in - ('xterm', 'xterm-color', 'xterm-256color', 'screen', - 'screen-256color', 'tmux', 'tmux-256color', 'rxvt-unicode', - 'rxvt-unicode-256color', 'linux', 'cygwin') or - (IS_WINDOWS_HOST and _EnableVTProcessingOnWindowsConsole()))): - gtest_color = 'yes' - else: - gtest_color = 'no' - env['GTEST_COLOR'] = gtest_color - _adb_shell([posixpath.join(device_out_dir, test)] + extra_command_line, env) - finally: - _adb_shell(['rm', '-rf', device_temp_dir]) + # Run the test on the device. Pass the test data root in the + # environment. + # + # Because the test will not run with its standard output attached to a + # pseudo-terminal device, gtest will not normally enable colored output, + # so mimic gtest’s own logic for deciding whether to enable color by + # checking this script’s own standard output connection. The whitelist + # of TERM values comes from gtest googletest/src/gtest.cc + # testing::internal::ShouldUseColor(). + env = {'CRASHPAD_TEST_DATA_ROOT': device_temp_dir} + gtest_color = os.environ.get('GTEST_COLOR') + if gtest_color in ('auto', None): + if (sys.stdout.isatty() and + (os.environ.get('TERM') + in ('xterm', 'xterm-color', 'xterm-256color', 'screen', + 'screen-256color', 'tmux', 'tmux-256color', 'rxvt-unicode', + 'rxvt-unicode-256color', 'linux', 'cygwin') or + (IS_WINDOWS_HOST and _EnableVTProcessingOnWindowsConsole()))): + gtest_color = 'yes' + else: + gtest_color = 'no' + env['GTEST_COLOR'] = gtest_color + _adb_shell([posixpath.join(device_out_dir, test)] + extra_command_line, + env) + finally: + _adb_shell(['rm', '-rf', device_temp_dir]) def _GetFuchsiaSDKRoot(): - arch = 'mac-amd64' if sys.platform == 'darwin' else 'linux-amd64' - return os.path.join(CRASHPAD_DIR, 'third_party', 'fuchsia', 'sdk', arch) + arch = 'mac-amd64' if sys.platform == 'darwin' else 'linux-amd64' + return os.path.join(CRASHPAD_DIR, 'third_party', 'fuchsia', 'sdk', arch) def _GenerateFuchsiaRuntimeDepsFiles(binary_dir, tests): - """Ensures a /.runtime_deps file exists for each test.""" - targets_file = os.path.join(binary_dir, 'targets.txt') - with open(targets_file, 'wb') as f: - f.write('//:' + '\n//:'.join(tests) + '\n') - gn_path = _FindGNFromBinaryDir(binary_dir) - subprocess.check_call( - [gn_path, '--root=' + CRASHPAD_DIR, 'gen', binary_dir, - '--runtime-deps-list-file=' + targets_file]) + """Ensures a /.runtime_deps file exists for each test.""" + targets_file = os.path.join(binary_dir, 'targets.txt') + with open(targets_file, 'wb') as f: + f.write('//:' + '\n//:'.join(tests) + '\n') + gn_path = _FindGNFromBinaryDir(binary_dir) + subprocess.check_call([ + gn_path, '--root=' + CRASHPAD_DIR, 'gen', binary_dir, + '--runtime-deps-list-file=' + targets_file + ]) - # Run again so that --runtime-deps-list-file isn't in the regen rule. See - # https://crbug.com/814816. - subprocess.check_call( - [gn_path, '--root=' + CRASHPAD_DIR, 'gen', binary_dir]) + # Run again so that --runtime-deps-list-file isn't in the regen rule. See + # https://crbug.com/814816. + subprocess.check_call( + [gn_path, '--root=' + CRASHPAD_DIR, 'gen', binary_dir]) def _HandleOutputFromFuchsiaLogListener(process, done_message): - """Pass through the output from |process| (which should be an instance of - Fuchsia's loglistener) until a special termination |done_message| is - encountered. + """Pass through the output from |process| (which should be an instance of + Fuchsia's loglistener) until a special termination |done_message| is + encountered. - Also attempts to determine if any tests failed by inspecting the log output, - and returns False if there were failures. - """ - success = True - while True: - line = process.stdout.readline().rstrip() - if 'FAILED TEST' in line: - success = False - elif done_message in line and 'echo ' not in line: - break - print(line) - return success + Also attempts to determine if any tests failed by inspecting the log output, + and returns False if there were failures. + """ + success = True + while True: + line = process.stdout.readline().rstrip() + if 'FAILED TEST' in line: + success = False + elif done_message in line and 'echo ' not in line: + break + print(line) + return success def _RunOnFuchsiaTarget(binary_dir, test, device_name, extra_command_line): - """Runs the given Fuchsia |test| executable on the given |device_name|. The - device must already be booted. + """Runs the given Fuchsia |test| executable on the given |device_name|. The + device must already be booted. - Copies the executable and its runtime dependencies as specified by GN to the - target in /tmp using `netcp`, runs the binary on the target, and logs output - back to stdout on this machine via `loglistener`. - """ - sdk_root = _GetFuchsiaSDKRoot() - - # Run loglistener and filter the output to know when the test is done. - loglistener_process = subprocess.Popen( - [os.path.join(sdk_root, 'tools', 'loglistener'), device_name], - stdout=subprocess.PIPE, stdin=open(os.devnull), stderr=open(os.devnull)) - - runtime_deps_file = os.path.join(binary_dir, test + '.runtime_deps') - with open(runtime_deps_file, 'rb') as f: - runtime_deps = f.read().splitlines() - - def netruncmd(*args): - """Runs a list of commands on the target device. Each command is escaped - by using pipes.quote(), and then each command is chained by shell ';'. + Copies the executable and its runtime dependencies as specified by GN to the + target in /tmp using `netcp`, runs the binary on the target, and logs output + back to stdout on this machine via `loglistener`. """ - netruncmd_path = os.path.join(sdk_root, 'tools', 'netruncmd') - final_args = ' ; '.join(' '.join(pipes.quote(x) for x in command) - for command in args) - subprocess.check_call([netruncmd_path, device_name, final_args]) + sdk_root = _GetFuchsiaSDKRoot() - try: - unique_id = uuid.uuid4().hex - test_root = '/tmp/%s_%s' % (test, unique_id) - tmp_root = test_root + '/tmp' - staging_root = test_root + '/pkg' + # Run loglistener and filter the output to know when the test is done. + loglistener_process = subprocess.Popen( + [os.path.join(sdk_root, 'tools', 'loglistener'), device_name], + stdout=subprocess.PIPE, + stdin=open(os.devnull), + stderr=open(os.devnull)) - # Make a staging directory tree on the target. - directories_to_create = [tmp_root, - '%s/bin' % staging_root, - '%s/assets' % staging_root] - netruncmd(['mkdir', '-p'] + directories_to_create) + runtime_deps_file = os.path.join(binary_dir, test + '.runtime_deps') + with open(runtime_deps_file, 'rb') as f: + runtime_deps = f.read().splitlines() - def netcp(local_path): - """Uses `netcp` to copy a file or directory to the device. Files located - inside the build dir are stored to /pkg/bin, otherwise to /pkg/assets. - .so files are stored somewhere completely different, into /boot/lib (!). - This is because the loader service does not yet correctly handle the - namespace in which the caller is being run, and so can only load .so files - from a couple hardcoded locations, the only writable one of which is - /boot/lib, so we copy all .so files there. This bug is filed upstream as - ZX-1619. - """ - in_binary_dir = local_path.startswith(binary_dir + '/') - if in_binary_dir: - if local_path.endswith('.so'): - target_path = os.path.join( - '/boot/lib', local_path[len(binary_dir)+1:]) + def netruncmd(*args): + """Runs a list of commands on the target device. Each command is escaped + by using pipes.quote(), and then each command is chained by shell ';'. + """ + netruncmd_path = os.path.join(sdk_root, 'tools', 'netruncmd') + final_args = ' ; '.join( + ' '.join(pipes.quote(x) for x in command) for command in args) + subprocess.check_call([netruncmd_path, device_name, final_args]) + + try: + unique_id = uuid.uuid4().hex + test_root = '/tmp/%s_%s' % (test, unique_id) + tmp_root = test_root + '/tmp' + staging_root = test_root + '/pkg' + + # Make a staging directory tree on the target. + directories_to_create = [ + tmp_root, + '%s/bin' % staging_root, + '%s/assets' % staging_root + ] + netruncmd(['mkdir', '-p'] + directories_to_create) + + def netcp(local_path): + """Uses `netcp` to copy a file or directory to the device. Files + located inside the build dir are stored to /pkg/bin, otherwise to + /pkg/assets. .so files are stored somewhere completely different, + into /boot/lib (!). This is because the loader service does not yet + correctly handle the namespace in which the caller is being run, and + so can only load .so files from a couple hardcoded locations, the + only writable one of which is /boot/lib, so we copy all .so files + there. This bug is filed upstream as ZX-1619. + """ + in_binary_dir = local_path.startswith(binary_dir + '/') + if in_binary_dir: + if local_path.endswith('.so'): + target_path = os.path.join('/boot/lib', + local_path[len(binary_dir) + 1:]) + else: + target_path = os.path.join(staging_root, 'bin', + local_path[len(binary_dir) + 1:]) + else: + relative_path = os.path.relpath(local_path, CRASHPAD_DIR) + target_path = os.path.join(staging_root, 'assets', + relative_path) + netcp_path = os.path.join(sdk_root, 'tools', 'netcp') + subprocess.check_call( + [netcp_path, local_path, device_name + ':' + target_path], + stderr=open(os.devnull)) + + # Copy runtime deps into the staging tree. + for dep in runtime_deps: + local_path = os.path.normpath(os.path.join(binary_dir, dep)) + if os.path.isdir(local_path): + for root, dirs, files in os.walk(local_path): + for f in files: + netcp(os.path.join(root, f)) + else: + netcp(local_path) + + done_message = 'TERMINATED: ' + unique_id + namespace_command = [ + 'namespace', '/pkg=' + staging_root, '/tmp=' + tmp_root, + '/svc=/svc', '--replace-child-argv0=/pkg/bin/' + test, '--', + staging_root + '/bin/' + test + ] + extra_command_line + netruncmd(namespace_command, ['echo', done_message]) + + success = _HandleOutputFromFuchsiaLogListener(loglistener_process, + done_message) + if not success: + raise subprocess.CalledProcessError(1, test) + finally: + netruncmd(['rm', '-rf', test_root]) + + +def _RunOnIOSTarget(binary_dir, test, is_xcuitest=False): + """Runs the given iOS |test| app on iPhone 8 with the default OS version.""" + + def xctest(binary_dir, test): + """Returns a dict containing the xctestrun data needed to run an + XCTest-based test app.""" + test_path = os.path.join(CRASHPAD_DIR, binary_dir) + module_data = { + 'TestBundlePath': os.path.join(test_path, test + '_module.xctest'), + 'TestHostPath': os.path.join(test_path, test + '.app'), + 'TestingEnvironmentVariables': { + 'DYLD_FRAMEWORK_PATH': '__TESTROOT__/Debug-iphonesimulator:', + 'DYLD_INSERT_LIBRARIES': + ('__PLATFORMS__/iPhoneSimulator.platform/Developer/' + 'usr/lib/libXCTestBundleInject.dylib'), + 'DYLD_LIBRARY_PATH': '__TESTROOT__/Debug-iphonesimulator', + 'IDEiPhoneInternalTestBundleName': test + '.app', + 'XCInjectBundleInto': '__TESTHOST__/' + test, + } + } + return {test: module_data} + + def xcuitest(binary_dir, test): + """Returns a dict containing the xctestrun data needed to run an + XCUITest-based test app.""" + + test_path = os.path.join(CRASHPAD_DIR, binary_dir) + runner_path = os.path.join(test_path, test + '_module-Runner.app') + bundle_path = os.path.join(runner_path, 'PlugIns', + test + '_module.xctest') + target_app_path = os.path.join(test_path, test + '.app') + module_data = { + 'IsUITestBundle': True, + 'IsXCTRunnerHostedTestBundle': True, + 'TestBundlePath': bundle_path, + 'TestHostPath': runner_path, + 'UITargetAppPath': target_app_path, + 'DependentProductPaths': [ + bundle_path, runner_path, target_app_path + ], + 'TestingEnvironmentVariables': { + 'DYLD_FRAMEWORK_PATH': '__TESTROOT__/Debug-iphonesimulator:', + 'DYLD_INSERT_LIBRARIES': + ('__PLATFORMS__/iPhoneSimulator.platform/Developer/' + 'usr/lib/libXCTestBundleInject.dylib'), + 'DYLD_LIBRARY_PATH': '__TESTROOT__/Debug-iphonesimulator', + 'XCInjectBundleInto': '__TESTHOST__/' + test + '_module-Runner', + }, + } + return {test: module_data} + + with tempfile.NamedTemporaryFile() as f: + import plistlib + + xctestrun_path = f.name + print(xctestrun_path) + if is_xcuitest: + plistlib.writePlist(xcuitest(binary_dir, test), xctestrun_path) else: - target_path = os.path.join( - staging_root, 'bin', local_path[len(binary_dir)+1:]) - else: - relative_path = os.path.relpath(local_path, CRASHPAD_DIR) - target_path = os.path.join(staging_root, 'assets', relative_path) - netcp_path = os.path.join(sdk_root, 'tools', 'netcp') - subprocess.check_call([netcp_path, local_path, - device_name + ':' + target_path], - stderr=open(os.devnull)) + plistlib.writePlist(xctest(binary_dir, test), xctestrun_path) - # Copy runtime deps into the staging tree. - for dep in runtime_deps: - local_path = os.path.normpath(os.path.join(binary_dir, dep)) - if os.path.isdir(local_path): - for root, dirs, files in os.walk(local_path): - for f in files: - netcp(os.path.join(root, f)) - else: - netcp(local_path) - - done_message = 'TERMINATED: ' + unique_id - namespace_command = [ - 'namespace', '/pkg=' + staging_root, '/tmp=' + tmp_root, '/svc=/svc', - '--replace-child-argv0=/pkg/bin/' + test, '--', - staging_root + '/bin/' + test] + extra_command_line - netruncmd(namespace_command, ['echo', done_message]) - - success = _HandleOutputFromFuchsiaLogListener( - loglistener_process, done_message) - if not success: - raise subprocess.CalledProcessError(1, test) - finally: - netruncmd(['rm', '-rf', test_root]) + subprocess.check_call([ + 'xcodebuild', 'test-without-building', '-xctestrun', xctestrun_path, + '-destination', 'platform=iOS Simulator,name=iPhone 8' + ]) # This script is primarily used from the waterfall so that the list of tests # that are run is maintained in-tree, rather than in a separate infrastructure # location in the recipe. def main(args): - parser = argparse.ArgumentParser(description='Run Crashpad unittests.') - parser.add_argument('binary_dir', help='Root of build dir') - parser.add_argument('test', nargs='*', help='Specific test(s) to run.') - parser.add_argument('--gtest_filter', - help='GTest filter applied to GTest binary runs.') - args = parser.parse_args() + parser = argparse.ArgumentParser(description='Run Crashpad unittests.') + parser.add_argument('binary_dir', help='Root of build dir') + parser.add_argument('test', nargs='*', help='Specific test(s) to run.') + parser.add_argument('--gtest_filter', + help='GTest filter applied to GTest binary runs.') + args = parser.parse_args() - # Tell 64-bit Windows tests where to find 32-bit test executables, for - # cross-bitted testing. This relies on the fact that the GYP build by default - # uses {Debug,Release} for the 32-bit build and {Debug,Release}_x64 for the - # 64-bit build. This is not a universally valid assumption, and if it’s not - # met, 64-bit tests that require 32-bit build output will disable themselves - # dynamically. - if (sys.platform == 'win32' and args.binary_dir.endswith('_x64') and - 'CRASHPAD_TEST_32_BIT_OUTPUT' not in os.environ): - binary_dir_32 = args.binary_dir[:-4] - if os.path.isdir(binary_dir_32): - os.environ['CRASHPAD_TEST_32_BIT_OUTPUT'] = binary_dir_32 + # Tell 64-bit Windows tests where to find 32-bit test executables, for + # cross-bitted testing. This relies on the fact that the GYP build by + # default uses {Debug,Release} for the 32-bit build and {Debug,Release}_x64 + # for the 64-bit build. This is not a universally valid assumption, and if + # it’s not met, 64-bit tests that require 32-bit build output will disable + # themselves dynamically. + if (sys.platform == 'win32' and args.binary_dir.endswith('_x64') and + 'CRASHPAD_TEST_32_BIT_OUTPUT' not in os.environ): + binary_dir_32 = args.binary_dir[:-4] + if os.path.isdir(binary_dir_32): + os.environ['CRASHPAD_TEST_32_BIT_OUTPUT'] = binary_dir_32 - target_os = _BinaryDirTargetOS(args.binary_dir) - is_android = target_os == 'android' - is_fuchsia = target_os == 'fuchsia' + target_os = _BinaryDirTargetOS(args.binary_dir) + is_android = target_os == 'android' + is_fuchsia = target_os == 'fuchsia' + is_ios = target_os == 'ios' - tests = [ - 'crashpad_client_test', - 'crashpad_handler_test', - 'crashpad_minidump_test', - 'crashpad_snapshot_test', - 'crashpad_test_test', - 'crashpad_util_test', - ] + tests = [ + 'crashpad_client_test', + 'crashpad_handler_test', + 'crashpad_minidump_test', + 'crashpad_snapshot_test', + 'crashpad_test_test', + 'crashpad_util_test', + ] - if is_android: - android_device = os.environ.get('ANDROID_DEVICE') - if not android_device: - adb_devices = subprocess.check_output(['adb', 'devices'], - shell=IS_WINDOWS_HOST) - devices = [] - for line in adb_devices.splitlines(): - line = line.decode('utf-8') - if (line == 'List of devices attached' or - re.match('^\* daemon .+ \*$', line) or - line == ''): - continue - (device, ignore) = line.split('\t') - devices.append(device) - if len(devices) != 1: - print("Please set ANDROID_DEVICE to your device's id", file=sys.stderr) - return 2 - android_device = devices[0] - print('Using autodetected Android device:', android_device) - elif is_fuchsia: - zircon_nodename = os.environ.get('ZIRCON_NODENAME') - if not zircon_nodename: - netls = os.path.join(_GetFuchsiaSDKRoot(), 'tools', 'netls') - popen = subprocess.Popen([netls, '--nowait'], stdout=subprocess.PIPE) - devices = popen.communicate()[0].splitlines() - if popen.returncode != 0 or len(devices) != 1: - print("Please set ZIRCON_NODENAME to your device's hostname", - file=sys.stderr) - return 2 - zircon_nodename = devices[0].strip().split()[1] - print('Using autodetected Fuchsia device:', zircon_nodename) - _GenerateFuchsiaRuntimeDepsFiles( - args.binary_dir, [t for t in tests if not t.endswith('.py')]) - elif IS_WINDOWS_HOST: - tests.append('snapshot/win/end_to_end_test.py') + if is_android: + android_device = os.environ.get('ANDROID_DEVICE') + if not android_device: + adb_devices = subprocess.check_output(['adb', 'devices'], + shell=IS_WINDOWS_HOST) + devices = [] + for line in adb_devices.splitlines(): + line = line.decode('utf-8') + if (line == 'List of devices attached' or + re.match('^\* daemon .+ \*$', line) or line == ''): + continue + (device, ignore) = line.split('\t') + devices.append(device) + if len(devices) != 1: + print("Please set ANDROID_DEVICE to your device's id", + file=sys.stderr) + return 2 + android_device = devices[0] + print('Using autodetected Android device:', android_device) + elif is_fuchsia: + zircon_nodename = os.environ.get('ZIRCON_NODENAME') + if not zircon_nodename: + netls = os.path.join(_GetFuchsiaSDKRoot(), 'tools', 'netls') + popen = subprocess.Popen([netls, '--nowait'], + stdout=subprocess.PIPE) + devices = popen.communicate()[0].splitlines() + if popen.returncode != 0 or len(devices) != 1: + print("Please set ZIRCON_NODENAME to your device's hostname", + file=sys.stderr) + return 2 + zircon_nodename = devices[0].strip().split()[1] + print('Using autodetected Fuchsia device:', zircon_nodename) + _GenerateFuchsiaRuntimeDepsFiles( + args.binary_dir, [t for t in tests if not t.endswith('.py')]) + elif is_ios: + tests.append('ios_crash_xcuitests') + elif IS_WINDOWS_HOST: + tests.append('snapshot/win/end_to_end_test.py') - if args.test: - for t in args.test: - if t not in tests: - print('Unrecognized test:', t, file=sys.stderr) - return 3 - tests = args.test + if args.test: + for t in args.test: + if t not in tests: + print('Unrecognized test:', t, file=sys.stderr) + return 3 + tests = args.test - for test in tests: - print('-' * 80) - print(test) - print('-' * 80) - if test.endswith('.py'): - subprocess.check_call( - [sys.executable, os.path.join(CRASHPAD_DIR, test), args.binary_dir]) - else: - extra_command_line = [] - if args.gtest_filter: - extra_command_line.append('--gtest_filter=' + args.gtest_filter) - if is_android: - _RunOnAndroidTarget(args.binary_dir, test, android_device, - extra_command_line) - elif is_fuchsia: - _RunOnFuchsiaTarget(args.binary_dir, test, zircon_nodename, - extra_command_line) - else: - subprocess.check_call([os.path.join(args.binary_dir, test)] + - extra_command_line) + for test in tests: + print('-' * 80) + print(test) + print('-' * 80) + if test.endswith('.py'): + subprocess.check_call([ + sys.executable, + os.path.join(CRASHPAD_DIR, test), args.binary_dir + ]) + else: + extra_command_line = [] + if args.gtest_filter: + extra_command_line.append('--gtest_filter=' + args.gtest_filter) + if is_android: + _RunOnAndroidTarget(args.binary_dir, test, android_device, + extra_command_line) + elif is_fuchsia: + _RunOnFuchsiaTarget(args.binary_dir, test, zircon_nodename, + extra_command_line) + elif is_ios: + _RunOnIOSTarget(args.binary_dir, + test, + is_xcuitest=test.startswith('ios')) + else: + subprocess.check_call([os.path.join(args.binary_dir, test)] + + extra_command_line) - return 0 + return 0 if __name__ == '__main__': - sys.exit(main(sys.argv[1:])) + sys.exit(main(sys.argv[1:])) diff --git a/build/test.gni b/build/test.gni index f46520b7..01fd7e79 100644 --- a/build/test.gni +++ b/build/test.gni @@ -18,9 +18,32 @@ if (crashpad_is_in_chromium) { import("//testing/test.gni") } else { template("test") { - executable(target_name) { - testonly = true - forward_variables_from(invoker, "*") + if (crashpad_is_ios) { + import("//third_party/mini_chromium/mini_chromium/build/ios/rules.gni") + + _launch_image_bundle_target = target_name + "_launch_image" + bundle_data(_launch_image_bundle_target) { + forward_variables_from(invoker, [ "testonly" ]) + sources = [ "//build/ios/Default.png" ] + outputs = [ "{{bundle_contents_dir}}/{{source_file_part}}" ] + } + + ios_xctest_test(target_name) { + testonly = true + xctest_module_target = "//test/ios:google_test_runner" + info_plist = "//build/ios/Unittest-Info.plist" + extra_substitutions = [ "GTEST_BUNDLE_ID_SUFFIX=$target_name" ] + forward_variables_from(invoker, "*") + if (!defined(deps)) { + deps = [] + } + deps += [ ":$_launch_image_bundle_target" ] + } + } else { + executable(target_name) { + testonly = true + forward_variables_from(invoker, "*") + } } } } diff --git a/client/BUILD.gn b/client/BUILD.gn index a074b503..b748c3fe 100644 --- a/client/BUILD.gn +++ b/client/BUILD.gn @@ -43,6 +43,13 @@ static_library("client") { ] } + if (crashpad_is_ios) { + sources += [ + "crash_report_database_mac.mm", + "crashpad_client_ios.cc", + ] + } + if (crashpad_is_linux || crashpad_is_android) { set_sources_assignment_filter([]) sources += [ @@ -77,22 +84,36 @@ static_library("client") { public_configs = [ "..:crashpad_config" ] - deps = [ - "../compat", + public_deps = [ "../third_party/mini_chromium:base", "../util", ] + deps = [] + if (crashpad_is_win) { libs = [ "rpcrt4.lib" ] cflags = [ "/wd4201" ] # nonstandard extension used : nameless struct/union } - if (crashpad_is_fuchsia && crashpad_is_in_fuchsia) { + # TODO(justincohen): Temporary dependency to bring up the iOS client. + if (crashpad_is_ios) { deps += [ - "//zircon/public/lib/fdio", + "../minidump", + "../snapshot", ] } + + if (crashpad_is_linux || crashpad_is_android) { + deps += [ "../third_party/lss" ] + } + + if (crashpad_is_fuchsia) { + deps += [ "../third_party/fuchsia" ] + if (crashpad_is_in_fuchsia) { + deps += [ "//zircon/public/lib/fdio" ] + } + } } source_set("client_test") { @@ -116,6 +137,17 @@ source_set("client_test") { sources += [ "crashpad_client_win_test.cc" ] } + if (crashpad_is_ios) { + sources += [ "crashpad_client_ios_test.mm" ] + sources -= [ + "annotation_list_test.cc", + "annotation_test.cc", + "crash_report_database_test.cc", + "prune_crash_reports_test.cc", + "settings_test.cc", + ] + } + if (crashpad_is_linux || crashpad_is_android) { sources += [ "crashpad_client_linux_test.cc" ] } @@ -131,9 +163,9 @@ source_set("client_test") { "../util", ] - data_deps = [ - "../handler:crashpad_handler", - ] + if (!crashpad_is_ios && !crashpad_is_fuchsia) { + data_deps = [ "../handler:crashpad_handler" ] + } if (crashpad_is_win) { data_deps += [ "../handler:crashpad_handler_console" ] diff --git a/client/client.gyp b/client/client.gyp index a23d0c86..dcb2d0eb 100644 --- a/client/client.gyp +++ b/client/client.gyp @@ -23,6 +23,7 @@ 'dependencies': [ '../compat/compat.gyp:crashpad_compat', '../third_party/mini_chromium/mini_chromium.gyp:base', + '../third_party/lss/lss.gyp:lss', '../util/util.gyp:crashpad_util', ], 'include_dirs': [ diff --git a/client/client_argv_handling.cc b/client/client_argv_handling.cc index 6933aac6..742a0008 100644 --- a/client/client_argv_handling.cc +++ b/client/client_argv_handling.cc @@ -61,14 +61,14 @@ std::vector BuildHandlerArgvStrings( 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()); +void StringVectorToCStringVector(const std::vector& strings, + std::vector* c_strings) { + c_strings->clear(); + c_strings->reserve(strings.size() + 1); + for (const auto& str : strings) { + c_strings->push_back(str.c_str()); } - argv->push_back(nullptr); + c_strings->push_back(nullptr); } } // namespace crashpad diff --git a/client/client_argv_handling.h b/client/client_argv_handling.h index d380b1a0..04c66d31 100644 --- a/client/client_argv_handling.h +++ b/client/client_argv_handling.h @@ -40,11 +40,11 @@ std::vector BuildHandlerArgvStrings( //! \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); +//! \param[in] strings A vector of string data. This vector must remain valid +//! for the lifetime of \a c_strings. +//! \param[out] c_strings A vector of pointers to the string data in \a strings. +void StringVectorToCStringVector(const std::vector& strings, + std::vector* c_strings); } // namespace crashpad diff --git a/client/crash_report_database.cc b/client/crash_report_database.cc index d300a8f9..eda0ef42 100644 --- a/client/crash_report_database.cc +++ b/client/crash_report_database.cc @@ -27,7 +27,8 @@ CrashReportDatabase::Report::Report() uploaded(false), last_upload_attempt_time(0), upload_attempts(0), - upload_explicitly_requested(false) {} + upload_explicitly_requested(false), + total_size(0u) {} CrashReportDatabase::NewReport::NewReport() : writer_(std::make_unique()), @@ -64,6 +65,15 @@ bool CrashReportDatabase::NewReport::Initialize( return true; } +FileReaderInterface* CrashReportDatabase::NewReport::Reader() { + auto reader = std::make_unique(); + if (!reader->Open(file_remover_.get())) { + return nullptr; + } + reader_ = std::move(reader); + return reader_.get(); +} + CrashReportDatabase::UploadReport::UploadReport() : Report(), reader_(std::make_unique()), diff --git a/client/crash_report_database.h b/client/crash_report_database.h index d115c74b..cc4b8dfa 100644 --- a/client/crash_report_database.h +++ b/client/crash_report_database.h @@ -15,6 +15,7 @@ #ifndef CRASHPAD_CLIENT_CRASH_REPORT_DATABASE_H_ #define CRASHPAD_CLIENT_CRASH_REPORT_DATABASE_H_ +#include #include #include @@ -98,6 +99,10 @@ class CrashReportDatabase { //! Whether this crash report was explicitly requested by user to be //! uploaded. This can be true only if report is in the 'pending' state. bool upload_explicitly_requested; + + //! The total size in bytes taken by the report, including any potential + //! attachments. + uint64_t total_size; }; //! \brief A crash report that is in the process of being written. @@ -108,9 +113,13 @@ class CrashReportDatabase { NewReport(); ~NewReport(); - //! An open FileWriter with which to write the report. + //! \brief An open FileWriter with which to write the report. FileWriter* Writer() const { return writer_.get(); } + //! \brief Returns a FileReaderInterface to the report, or `nullptr` with a + //! message logged. + FileReaderInterface* Reader(); + //! A unique identifier by which this report will always be known to the //! database. const UUID& ReportID() const { return uuid_; } @@ -137,6 +146,7 @@ class CrashReportDatabase { const base::FilePath::StringType& extension); std::unique_ptr writer_; + std::unique_ptr reader_; ScopedRemoveFile file_remover_; std::vector> attachment_writers_; std::vector attachment_removers_; @@ -163,7 +173,7 @@ class CrashReportDatabase { //! This is not implemented on macOS or Windows. std::map GetAttachments() const { return attachment_map_; - }; + } private: friend class CrashReportDatabase; diff --git a/client/crash_report_database_generic.cc b/client/crash_report_database_generic.cc index 8e932374..aeaf2af4 100644 --- a/client/crash_report_database_generic.cc +++ b/client/crash_report_database_generic.cc @@ -15,6 +15,7 @@ #include "client/crash_report_database.h" #include +#include #include #include @@ -25,6 +26,7 @@ #include "util/file/directory_reader.h" #include "util/file/filesystem.h" #include "util/misc/initialization_state_dcheck.h" +#include "util/misc/memory_sanitizer.h" namespace crashpad { @@ -160,6 +162,34 @@ class ScopedLockFile { DISALLOW_COPY_AND_ASSIGN(ScopedLockFile); }; +off_t GetFileSize(const base::FilePath& filepath) { + struct stat statbuf; + if (stat(filepath.value().c_str(), &statbuf) == 0) { + return statbuf.st_size; + } + PLOG(ERROR) << "stat " << filepath.value(); + return 0; +} + +void AddAttachmentSize(const base::FilePath& attachments_dir, uint64_t* size) { + // Early return if the attachment directory does not exist. + if (!IsDirectory(attachments_dir, /*allow_symlinks=*/false)) { + return; + } + DirectoryReader reader; + if (!reader.Open(attachments_dir)) { + return; + } + base::FilePath attachment_filename; + DirectoryReader::Result result; + while ((result = reader.NextFile(&attachment_filename)) == + DirectoryReader::Result::kSuccess) { + const base::FilePath attachment_filepath( + attachments_dir.Append(attachment_filename)); + *size += GetFileSize(attachment_filepath); + } +} + } // namespace class CrashReportDatabaseGeneric : public CrashReportDatabase { @@ -253,7 +283,7 @@ class CrashReportDatabaseGeneric : public CrashReportDatabase { void RemoveAttachmentsByUUID(const UUID& uuid); // Reads the metadata for a report from path and returns it in report. - static bool ReadMetadata(const base::FilePath& path, Report* report); + bool ReadMetadata(const base::FilePath& path, Report* report); // Wraps ReadMetadata and removes the report from the database on failure. bool CleaningReadMetadata(const base::FilePath& path, Report* report); @@ -303,6 +333,9 @@ void CrashReportDatabase::UploadReport::InitializeAttachments() { base::FilePath attachments_dir = static_cast(database_)->AttachmentsPath( uuid); + if (!IsDirectory(attachments_dir, /*allow_symlinks=*/false)) { + return; + } DirectoryReader reader; if (!reader.Open(attachments_dir)) { return; @@ -842,7 +875,6 @@ 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; } @@ -883,6 +915,9 @@ void CrashReportDatabaseGeneric::CleanOrphanedAttachments() { void CrashReportDatabaseGeneric::RemoveAttachmentsByUUID(const UUID& uuid) { base::FilePath attachments_dir = AttachmentsPath(uuid); + if (!IsDirectory(attachments_dir, /*allow_symlinks=*/false)) { + return; + } DirectoryReader reader; if (!reader.Open(attachments_dir)) { return; @@ -899,7 +934,6 @@ void CrashReportDatabaseGeneric::RemoveAttachmentsByUUID(const UUID& uuid) { LoggingRemoveDirectory(attachments_dir); } -// static bool CrashReportDatabaseGeneric::ReadMetadata(const base::FilePath& path, Report* report) { const base::FilePath metadata_path( @@ -910,7 +944,8 @@ bool CrashReportDatabaseGeneric::ReadMetadata(const base::FilePath& path, return false; } - if (!report->uuid.InitializeFromString( + UUID uuid; + if (!uuid.InitializeFromString( path.BaseName().RemoveFinalExtension().value())) { LOG(ERROR) << "Couldn't interpret report uuid"; return false; @@ -930,6 +965,12 @@ bool CrashReportDatabaseGeneric::ReadMetadata(const base::FilePath& path, return false; } + // Seed the total size with the main report size and then add the sizes of any + // potential attachments. + uint64_t total_size = GetFileSize(path); + AddAttachmentSize(AttachmentsPath(uuid), &total_size); + + report->uuid = uuid; report->upload_attempts = metadata.upload_attempts; report->last_upload_attempt_time = metadata.last_upload_attempt_time; report->creation_time = metadata.creation_time; @@ -937,6 +978,7 @@ bool CrashReportDatabaseGeneric::ReadMetadata(const base::FilePath& path, report->upload_explicitly_requested = (metadata.attributes & kAttributeUploadExplicitlyRequested) != 0; report->file_path = path; + report->total_size = total_size; return true; } @@ -966,6 +1008,11 @@ bool CrashReportDatabaseGeneric::WriteNewMetadata(const base::FilePath& path) { } ReportMetadata metadata; +#if defined(MEMORY_SANITIZER) + // memset() + re-initialization is required to zero padding bytes for MSan. + memset(&metadata, 0, sizeof(metadata)); +#endif // defined(MEMORY_SANITIZER) + metadata = {}; metadata.creation_time = time(nullptr); return LoggingWriteFile(handle.get(), &metadata, sizeof(metadata)); @@ -986,6 +1033,11 @@ bool CrashReportDatabaseGeneric::WriteMetadata(const base::FilePath& path, } ReportMetadata metadata; +#if defined(MEMORY_SANITIZER) + // memset() + re-initialization is required to zero padding bytes for MSan. + memset(&metadata, 0, sizeof(metadata)); +#endif // defined(MEMORY_SANITIZER) + metadata = {}; metadata.creation_time = report.creation_time; metadata.last_upload_attempt_time = report.last_upload_attempt_time; metadata.upload_attempts = report.upload_attempts; diff --git a/client/crash_report_database_mac.mm b/client/crash_report_database_mac.mm index 3106dc2c..1f8fec9b 100644 --- a/client/crash_report_database_mac.mm +++ b/client/crash_report_database_mac.mm @@ -29,6 +29,7 @@ #include "base/mac/scoped_nsautorelease_pool.h" #include "base/posix/eintr_wrapper.h" #include "base/scoped_generic.h" +#include "base/stl_util.h" #include "base/strings/string_piece.h" #include "base/strings/stringprintf.h" #include "base/strings/sys_string_conversions.h" @@ -279,7 +280,7 @@ bool CrashReportDatabaseMac::Initialize(bool may_create) { } // Create the three processing directories for the database. - for (size_t i = 0; i < arraysize(kReportDirectories); ++i) { + for (size_t i = 0; i < base::size(kReportDirectories); ++i) { if (!CreateOrEnsureDirectoryExists(base_dir_.Append(kReportDirectories[i]))) return false; } @@ -681,6 +682,14 @@ bool CrashReportDatabaseMac::ReadReportMetadataLocked( return false; } + // There are no attachments on Mac so the total size is the main report size. + struct stat statbuf; + if (stat(path.value().c_str(), &statbuf) != 0) { + PLOG(ERROR) << "stat " << path.value(); + return false; + } + report->total_size = statbuf.st_size; + return true; } diff --git a/client/crash_report_database_test.cc b/client/crash_report_database_test.cc index 2dbb4fc1..20512a45 100644 --- a/client/crash_report_database_test.cc +++ b/client/crash_report_database_test.cc @@ -20,7 +20,6 @@ #include "test/errors.h" #include "test/file.h" #include "test/filesystem.h" -#include "test/gtest_disabled.h" #include "test/scoped_temp_dir.h" #include "util/file/file_io.h" #include "util/file/filesystem.h" @@ -31,8 +30,7 @@ namespace { class CrashReportDatabaseTest : public testing::Test { public: - CrashReportDatabaseTest() { - } + CrashReportDatabaseTest() {} protected: // testing::Test: @@ -41,9 +39,7 @@ class CrashReportDatabaseTest : public testing::Test { ASSERT_TRUE(db_); } - void ResetDatabase() { - db_.reset(); - } + void ResetDatabase() { db_.reset(); } CrashReportDatabase* db() { return db_.get(); } base::FilePath path() const { @@ -57,6 +53,12 @@ class CrashReportDatabaseTest : public testing::Test { static constexpr char kTest[] = "test"; ASSERT_TRUE(new_report->Writer()->Write(kTest, sizeof(kTest))); + char contents[sizeof(kTest)]; + FileReaderInterface* reader = new_report->Reader(); + ASSERT_TRUE(reader->ReadExactly(contents, sizeof(contents))); + EXPECT_EQ(memcmp(contents, kTest, sizeof(contents)), 0); + EXPECT_EQ(reader->ReadExactly(contents, 1), 0); + UUID uuid; EXPECT_EQ(db_->FinishedWritingCrashReport(std::move(new_report), &uuid), CrashReportDatabase::kNoError); @@ -101,6 +103,7 @@ class CrashReportDatabaseTest : public testing::Test { EXPECT_EQ(report.last_upload_attempt_time, 0); EXPECT_EQ(report.upload_attempts, 0); EXPECT_FALSE(report.upload_explicitly_requested); + EXPECT_GE(report.total_size, 0u); } void RelocateDatabase() { @@ -673,7 +676,7 @@ TEST_F(CrashReportDatabaseTest, RequestUpload) { TEST_F(CrashReportDatabaseTest, Attachments) { #if defined(OS_MACOSX) || defined(OS_WIN) // Attachments aren't supported on Mac and Windows yet. - DISABLED_TEST(); + GTEST_SKIP(); #else std::unique_ptr new_report; ASSERT_EQ(db()->PrepareNewCrashReport(&new_report), @@ -719,7 +722,7 @@ TEST_F(CrashReportDatabaseTest, Attachments) { TEST_F(CrashReportDatabaseTest, OrphanedAttachments) { #if defined(OS_MACOSX) || defined(OS_WIN) // Attachments aren't supported on Mac and Windows yet. - DISABLED_TEST(); + GTEST_SKIP(); #else // TODO: This is using paths that are specific to the generic implementation // and will need to be generalized for Mac and Windows. @@ -835,6 +838,66 @@ TEST_F(CrashReportDatabaseTest, CleanBrokenDatabase) { } #endif // !OS_MACOSX && !OS_WIN +TEST_F(CrashReportDatabaseTest, TotalSize_MainReportOnly) { + std::unique_ptr new_report; + ASSERT_EQ(db()->PrepareNewCrashReport(&new_report), + CrashReportDatabase::kNoError); + + // Main report. + static constexpr char main_report_data[] = "dlbvandslhb"; + ASSERT_TRUE( + new_report->Writer()->Write(main_report_data, sizeof(main_report_data))); + + UUID uuid; + ASSERT_EQ(db()->FinishedWritingCrashReport(std::move(new_report), &uuid), + CrashReportDatabase::kNoError); + + CrashReportDatabase::Report report; + ASSERT_EQ(db()->LookUpCrashReport(uuid, &report), + CrashReportDatabase::kNoError); + + EXPECT_EQ(report.total_size, sizeof(main_report_data)); +} + +TEST_F(CrashReportDatabaseTest, GetReportSize_RightSizeWithAttachments) { +#if defined(OS_MACOSX) || defined(OS_WIN) + // Attachments aren't supported on Mac and Windows yet. + return; +#else + std::unique_ptr new_report; + ASSERT_EQ(db()->PrepareNewCrashReport(&new_report), + CrashReportDatabase::kNoError); + + // Main report. + static constexpr char main_report_data[] = "dlbvandslhb"; + ASSERT_TRUE( + new_report->Writer()->Write(main_report_data, sizeof(main_report_data))); + + // First attachment. + FileWriter* attachment_1 = new_report->AddAttachment("my_attachment_1"); + ASSERT_NE(attachment_1, nullptr); + static constexpr char attachment_1_data[] = "vKDnidhvbiudshoihbvdsoiuh nhh"; + attachment_1->Write(attachment_1_data, sizeof(attachment_1_data)); + + // Second attachment. + FileWriter* attachment_2 = new_report->AddAttachment("my_attachment_2"); + ASSERT_NE(attachment_2, nullptr); + static constexpr char attachment_2_data[] = "sgvsvgusiyguysigfkhpmo-["; + attachment_2->Write(attachment_2_data, sizeof(attachment_2_data)); + + UUID uuid; + ASSERT_EQ(db()->FinishedWritingCrashReport(std::move(new_report), &uuid), + CrashReportDatabase::kNoError); + + CrashReportDatabase::Report report; + ASSERT_EQ(db()->LookUpCrashReport(uuid, &report), + CrashReportDatabase::kNoError); + EXPECT_EQ(report.total_size, + sizeof(main_report_data) + sizeof(attachment_1_data) + + sizeof(attachment_2_data)); +#endif +} + } // namespace } // namespace test } // namespace crashpad diff --git a/client/crash_report_database_win.cc b/client/crash_report_database_win.cc index 89677706..15137d0c 100644 --- a/client/crash_report_database_win.cc +++ b/client/crash_report_database_win.cc @@ -458,7 +458,18 @@ void Metadata::Read() { LOG(ERROR) << "invalid string table index"; return; } - reports.push_back(ReportDisk(record, report_dir_, string_table)); + ReportDisk report_disk(record, report_dir_, string_table); + + // There are no attachments on Windows so the total size is the main + // report size. + struct _stati64 statbuf; + if (_wstat64(report_disk.file_path.value().c_str(), &statbuf) == 0) { + report_disk.total_size = statbuf.st_size; + } else { + LOG(ERROR) << "failed to stat report"; + } + + reports.push_back(report_disk); } } reports_.swap(reports); diff --git a/client/crashpad_client.h b/client/crashpad_client.h index ae409d43..f5e02c9c 100644 --- a/client/crashpad_client.h +++ b/client/crashpad_client.h @@ -16,6 +16,7 @@ #define CRASHPAD_CLIENT_CRASHPAD_CLIENT_H_ #include +#include #include #include @@ -24,6 +25,7 @@ #include "base/files/file_path.h" #include "base/macros.h" #include "build/build_config.h" +#include "util/file/file_io.h" #include "util/misc/capture_context.h" #if defined(OS_MACOSX) @@ -78,6 +80,10 @@ class CrashpadClient { //! On Fuchsia, this method binds to the exception port of the current default //! job, and starts a Crashpad handler to monitor that port. //! + //! On Linux, this method starts a Crashpad handler, connected to this process + //! via an `AF_UNIX` socket pair and installs signal handlers to request crash + //! dumps on the client's socket end. + //! //! \param[in] handler The path to a Crashpad handler executable. //! \param[in] database The path to a Crashpad database. The handler will be //! started with this path as its `--database` argument. @@ -112,6 +118,199 @@ class CrashpadClient { bool restartable, bool asynchronous_start); +#if defined(OS_ANDROID) || defined(OS_LINUX) || DOXYGEN + //! \brief Retrieve the socket and process ID for the handler. + //! + //! `StartHandler()` must have successfully been called before calling this + //! method. + //! + //! \param[out] sock The socket connected to the handler, if not `nullptr`. + //! \param[out] pid The handler's process ID, if not `nullptr`. + //! \return `true` on success. Otherwise `false` with a message logged. + static bool GetHandlerSocket(int* sock, pid_t* pid); + + //! \brief Sets the socket to a presumably-running Crashpad handler process + //! which was started with StartHandler(). + //! + //! This method installs a signal handler to request crash dumps on \a sock. + //! + //! \param[in] sock A socket connected to a Crashpad handler. + //! \param[in] pid The process ID of the handler, used to set the handler as + //! this process' ptracer. 0 indicates it is not necessary to set the + //! handler as this process' ptracer. -1 indicates that the handler's + //! process ID should be determined by communicating over the socket. + bool SetHandlerSocket(ScopedFileHandle sock, pid_t pid); +#endif // OS_ANDROID || OS_LINUX || DOXYGEN + +#if defined(OS_ANDROID) || DOXYGEN + //! \brief Installs a signal handler to execute `/system/bin/app_process` and + //! load a Java class in response to a crash. + //! + //! \param[in] class_name The fully qualified class name to load, which must + //! define a `main()` method to be invoked by `app_process`. Arguments + //! will be passed to this method as though it were the Crashpad handler. + //! This class is expected to load a native library defining + //! crashpad::HandlerMain() and pass the arguments to it. + //! \param[in] env A vector of environment variables of the form `var=value` + //! defining the environment in which to execute `app_process`. If this + //! value is `nullptr`, the application's environment at the time of the + //! crash will be used. + //! \param[in] database The path to a Crashpad database. The handler will be + //! started with this path as its `--database` argument. + //! \param[in] metrics_dir The path to an already existing directory where + //! metrics files can be stored. The handler will be started with this + //! path as its `--metrics-dir` argument. + //! \param[in] url The URL of an upload server. The handler will be started + //! with this URL as its `--url` argument. + //! \param[in] annotations Process annotations to set in each crash report. + //! The handler will be started with an `--annotation` argument for each + //! element in this map. + //! \param[in] arguments Additional arguments to pass to the Crashpad handler. + //! Arguments passed in other parameters and arguments required to perform + //! the handshake are the responsibility of this method, and must not be + //! specified in this parameter. + //! + //! \return `true` on success, `false` on failure with a message logged. + bool StartJavaHandlerAtCrash( + const std::string& class_name, + const std::vector* env, + const base::FilePath& database, + const base::FilePath& metrics_dir, + const std::string& url, + const std::map& annotations, + const std::vector& arguments); + + //! \brief Executes `/system/bin/app_process` and loads a Java class. + //! + //! \param[in] class_name The fully qualified class name to load, which must + //! define a `main()` method to be invoked by `app_process`. Arguments + //! will be passed to this method as though it were the Crashpad handler. + //! This class is expected to load a native library defining + //! crashpad::HandlerMain() and pass the arguments to it. + //! \param[in] env A vector of environment variables of the form `var=value` + //! defining the environment in which to execute `app_process`. If this + //! value is `nullptr`, the application's current environment will be + //! used. + //! \param[in] database The path to a Crashpad database. The handler will be + //! started with this path as its `--database` argument. + //! \param[in] metrics_dir The path to an already existing directory where + //! metrics files can be stored. The handler will be started with this + //! path as its `--metrics-dir` argument. + //! \param[in] url The URL of an upload server. The handler will be started + //! with this URL as its `--url` argument. + //! \param[in] annotations Process annotations to set in each crash report. + //! The handler will be started with an `--annotation` argument for each + //! element in this map. + //! \param[in] arguments Additional arguments to pass to the Crashpad handler. + //! Arguments passed in other parameters and arguments required to perform + //! the handshake are the responsibility of this method, and must not be + //! specified in this parameter. + //! \param[in] socket The server end of a socket pair. The client end should + //! be used with an ExceptionHandlerClient. + //! + //! \return `true` on success, `false` on failure with a message logged. + static bool StartJavaHandlerForClient( + const std::string& class_name, + const std::vector* env, + const base::FilePath& database, + const base::FilePath& metrics_dir, + const std::string& url, + const std::map& annotations, + const std::vector& arguments, + int socket); + + //! \brief Installs a signal handler to start a Crashpad handler process by + //! loading it with `/system/bin/linker`. + //! + //! This method is only supported by Android Q+. + //! + //! \param[in] handler_trampoline The path to a Crashpad handler trampoline + //! executable, possibly located within an apk, e.g. + //! "/data/app/myapk.apk!/myabi/libcrashpad_handler_trampoline.so". + //! \param[in] handler_library The name of a library exporting the symbol + //! `CrashpadHandlerMain()`. The path to this library must be present in + //! `LD_LIBRARY_PATH`. + //! \param[in] is_64_bit `true` if \a handler_trampoline and \a + //! handler_library are 64-bit objects. They must have the same bitness. + //! \param[in] env A vector of environment variables of the form `var=value` + //! defining the environment in which to execute `app_process`. If this + //! value is `nullptr`, the application's environment at the time of the + //! crash will be used. + //! \param[in] database The path to a Crashpad database. The handler will be + //! started with this path as its `--database` argument. + //! \param[in] metrics_dir The path to an already existing directory where + //! metrics files can be stored. The handler will be started with this + //! path as its `--metrics-dir` argument. + //! \param[in] url The URL of an upload server. The handler will be started + //! with this URL as its `--url` argument. + //! \param[in] annotations Process annotations to set in each crash report. + //! The handler will be started with an `--annotation` argument for each + //! element in this map. + //! \param[in] arguments Additional arguments to pass to the Crashpad handler. + //! Arguments passed in other parameters and arguments required to perform + //! the handshake are the responsibility of this method, and must not be + //! specified in this parameter. + //! + //! \return `true` on success, `false` on failure with a message logged. + bool StartHandlerWithLinkerAtCrash( + const std::string& handler_trampoline, + const std::string& handler_library, + bool is_64_bit, + const std::vector* env, + const base::FilePath& database, + const base::FilePath& metrics_dir, + const std::string& url, + const std::map& annotations, + const std::vector& arguments); + + //! \brief Starts a Crashpad handler process with an initial client by loading + //! it with `/system/bin/linker`. + //! + //! This method is only supported by Android Q+. + //! + //! \param[in] handler_trampoline The path to a Crashpad handler trampoline + //! executable, possibly located within an apk, e.g. + //! "/data/app/myapk.apk!/myabi/libcrashpad_handler_trampoline.so". + //! \param[in] handler_library The name of a library exporting the symbol + //! `CrashpadHandlerMain()`. The path to this library must be present in + //! `LD_LIBRARY_PATH`. + //! \param[in] is_64_bit `true` if \a handler_trampoline and \a + //! handler_library are 64-bit objects. They must have the same bitness. + //! \param[in] env A vector of environment variables of the form `var=value` + //! defining the environment in which to execute `app_process`. If this + //! value is `nullptr`, the application's current environment will be + //! used. + //! \param[in] database The path to a Crashpad database. The handler will be + //! started with this path as its `--database` argument. + //! \param[in] metrics_dir The path to an already existing directory where + //! metrics files can be stored. The handler will be started with this + //! path as its `--metrics-dir` argument. + //! \param[in] url The URL of an upload server. The handler will be started + //! with this URL as its `--url` argument. + //! \param[in] annotations Process annotations to set in each crash report. + //! The handler will be started with an `--annotation` argument for each + //! element in this map. + //! \param[in] arguments Additional arguments to pass to the Crashpad handler. + //! Arguments passed in other parameters and arguments required to perform + //! the handshake are the responsibility of this method, and must not be + //! specified in this parameter. + //! \param[in] socket The server end of a socket pair. The client end should + //! be used with an ExceptionHandlerClient. + //! + //! \return `true` on success, `false` on failure with a message logged. + static bool StartHandlerWithLinkerForClient( + const std::string& handler_trampoline, + const std::string& handler_library, + bool is_64_bit, + const std::vector* env, + const base::FilePath& database, + const base::FilePath& metrics_dir, + const std::string& url, + const std::map& annotations, + const std::vector& arguments, + int socket); +#endif // OS_ANDROID || DOXYGEN + #if defined(OS_LINUX) || defined(OS_ANDROID) || DOXYGEN //! \brief Installs a signal handler to launch a handler process in reponse to //! a crash. @@ -135,7 +334,7 @@ class CrashpadClient { //! specified in this parameter. //! //! \return `true` on success, `false` on failure with a message logged. - static bool StartHandlerAtCrash( + bool StartHandlerAtCrash( const base::FilePath& handler, const base::FilePath& database, const base::FilePath& metrics_dir, @@ -188,6 +387,12 @@ class CrashpadClient { //! CaptureContext() or similar. static void DumpWithoutCrash(NativeCPUContext* context); + //! \brief Disables any installed crash handler, including any + //! FirstChanceHandler and crashes the current process. + //! + //! \param[in] message A message to be logged before crashing. + static void CrashWithoutDump(const std::string& message); + //! \brief The type for custom handlers installed by clients. using FirstChanceHandler = bool (*)(int, siginfo_t*, ucontext_t*); @@ -209,8 +414,38 @@ class CrashpadClient { //! \param[in] handler The custom crash signal handler to install. static void SetFirstChanceExceptionHandler(FirstChanceHandler handler); + //! \brief Configures a set of signals that shouldn't have Crashpad signal + //! handlers installed. + //! + //! This method should be called before calling StartHandler(), + //! SetHandlerSocket(), or other methods that install Crashpad signal + //! handlers. + //! + //! \param[in] unhandled_signals The set of unhandled signals + void SetUnhandledSignals(const std::set& unhandled_signals); #endif // OS_LINUX || OS_ANDROID || DOXYGEN +#if defined(OS_IOS) || DOXYGEN + //! \brief Configures the process to direct its crashes to the iOS in-process + //! Crashpad handler. + //! + //! This method is only defined on iOS. + //! + //! TODO(justincohen): This method will need to take database, metrics_dir, + //! url and annotations eventually. + void StartCrashpadInProcessHandler(); + + // TODO(justincohen): This method is purely for bringing up iOS interfaces. + //! \brief Requests that the handler capture a dump even though there hasn't + //! been a crash. + //! + //! A handler must have already been installed before calling this method. + //! + //! \param[in] context A NativeCPUContext, generally captured by + //! CaptureContext() or similar. + static void DumpWithoutCrash(NativeCPUContext* context); +#endif + #if defined(OS_MACOSX) || DOXYGEN //! \brief Sets the process’ crash handler to a Mach service registered with //! the bootstrap server. @@ -384,12 +619,24 @@ class CrashpadClient { static void UseSystemDefaultHandler(); #endif +#if defined(OS_CHROMEOS) + //! \brief Sets a timestamp on the signal handler to be passed on to + //! crashpad_handler and then eventually Chrome OS's crash_reporter. + //! + //! \note This method is used by clients that use `StartHandler()` to start + //! a handler and not by clients that use any other handler starting + //! methods. + static void SetCrashLoopBefore(uint64_t crash_loop_before_time); +#endif + private: #if defined(OS_MACOSX) base::mac::ScopedMachSendRight exception_port_; #elif defined(OS_WIN) std::wstring ipc_pipe_; ScopedKernelHANDLE handler_start_thread_; +#elif defined(OS_LINUX) || defined(OS_ANDROID) + std::set unhandled_signals_; #endif // OS_MACOSX DISALLOW_COPY_AND_ASSIGN(CrashpadClient); diff --git a/client/crashpad_client_fuchsia.cc b/client/crashpad_client_fuchsia.cc index 0d1b65bf..3da1f765 100644 --- a/client/crashpad_client_fuchsia.cc +++ b/client/crashpad_client_fuchsia.cc @@ -15,15 +15,15 @@ #include "client/crashpad_client.h" #include -#include +#include +#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 { @@ -43,52 +43,44 @@ bool CrashpadClient::StartHandler( 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); + StringVectorToCStringVector(argv_strings, &argv); - // Follow the same protocol as devmgr and crashlogger in Zircon (that is, - // process handle as handle 0, with type USER0, exception port handle as - // handle 1, also with type PA_USER0) so that it's trivial to replace - // crashlogger with crashpad_handler. The exception port is passed on, so - // released here. Currently it is assumed that this process's default job - // handle is the exception port that should be monitored. In the future, it - // might be useful for this to be configurable by the client. - constexpr size_t kActionCount = 2; - fdio_spawn_action_t actions[] = { - {.action = FDIO_SPAWN_ACTION_ADD_HANDLE, - .h = {.id = PA_HND(PA_USER0, 0), .handle = ZX_HANDLE_INVALID}}, - {.action = FDIO_SPAWN_ACTION_ADD_HANDLE, - .h = {.id = PA_HND(PA_USER0, 1), .handle = ZX_HANDLE_INVALID}}, - }; - - status = zx_handle_duplicate( - zx_job_default(), ZX_RIGHT_SAME_RIGHTS, &actions[0].h.handle); + // Set up handles to send to the spawned process: + // 0. PA_USER0 job + // 1. PA_USER0 exception channel + // + // Currently it is assumed that this process's default job handle is the + // exception channel that should be monitored. In the future, it might be + // useful for this to be configurable by the client. + zx::job job; + zx_status_t status = + zx::job::default_job()->duplicate(ZX_RIGHT_SAME_RIGHTS, &job); if (status != ZX_OK) { ZX_LOG(ERROR, status) << "zx_handle_duplicate"; return false; } - actions[1].h.handle = exception_port.release(); + + zx::channel exception_channel; + status = job.create_exception_channel(0, &exception_channel); + if (status != ZX_OK) { + ZX_LOG(ERROR, status) << "zx_task_create_exception_channel"; + return false; + } + + constexpr size_t kActionCount = 2; + fdio_spawn_action_t actions[] = { + {.action = FDIO_SPAWN_ACTION_ADD_HANDLE, + .h = {.id = PA_HND(PA_USER0, 0), .handle = job.release()}}, + {.action = FDIO_SPAWN_ACTION_ADD_HANDLE, + .h = {.id = PA_HND(PA_USER0, 1), .handle = exception_channel.release()}}, + }; char error_message[FDIO_SPAWN_ERR_MSG_MAX_LENGTH]; - zx_handle_t child_raw; + zx::process child; // TODO(scottmg): https://crashpad.chromium.org/bug/196, FDIO_SPAWN_CLONE_ALL // is useful during bringup, but should probably be made minimal for real // usage. @@ -99,9 +91,8 @@ bool CrashpadClient::StartHandler( nullptr, kActionCount, actions, - &child_raw, + child.reset_and_get_address(), error_message); - base::ScopedZxHandle child(child_raw); if (status != ZX_OK) { ZX_LOG(ERROR, status) << "fdio_spawn_etc: " << error_message; return false; diff --git a/client/crashpad_client_ios.cc b/client/crashpad_client_ios.cc new file mode 100644 index 00000000..342e1090 --- /dev/null +++ b/client/crashpad_client_ios.cc @@ -0,0 +1,228 @@ +// Copyright 2020 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "client/crashpad_client.h" + +#include + +#include + +#include "base/logging.h" +#include "base/mac/mach_logging.h" +#include "base/mac/scoped_mach_port.h" +#include "base/stl_util.h" +#include "snapshot/ios/process_snapshot_ios.h" +#include "util/ios/exception_processor.h" +#include "util/ios/ios_system_data_collector.h" +#include "util/mach/exc_server_variants.h" +#include "util/mach/exception_ports.h" +#include "util/mach/mach_extensions.h" +#include "util/mach/mach_message.h" +#include "util/mach/mach_message_server.h" +#include "util/misc/initialization_state_dcheck.h" +#include "util/posix/signals.h" +#include "util/thread/thread.h" + +namespace crashpad { + +namespace { + +// A base class for signal handler and Mach exception server. +class CrashHandler : public Thread, public UniversalMachExcServer::Interface { + public: + static CrashHandler* Get() { + static CrashHandler* instance = new CrashHandler(); + return instance; + } + + void Initialize() { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + InstallMachExceptionHandler(); + CHECK(Signals::InstallHandler(SIGABRT, CatchSignal, 0, &old_action_)); + INITIALIZATION_STATE_SET_VALID(initialized_); + } + + void DumpWithoutCrash(NativeCPUContext* context) { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + mach_exception_data_type_t code[2] = {}; + static constexpr int kSimulatedException = -1; + HandleMachException(MACH_EXCEPTION_CODES, + mach_thread_self(), + kSimulatedException, + code, + base::size(code), + MACHINE_THREAD_STATE, + reinterpret_cast(context), + MACHINE_THREAD_STATE_COUNT); + } + + private: + CrashHandler() = default; + + void InstallMachExceptionHandler() { + exception_port_.reset(NewMachPort(MACH_PORT_RIGHT_RECEIVE)); + CHECK(exception_port_.is_valid()); + + kern_return_t kr = mach_port_insert_right(mach_task_self(), + exception_port_.get(), + exception_port_.get(), + MACH_MSG_TYPE_MAKE_SEND); + MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_port_insert_right"; + + // TODO: Use SwapExceptionPort instead and put back EXC_MASK_BREAKPOINT. + const exception_mask_t mask = + ExcMaskAll() & + ~(EXC_MASK_EMULATION | EXC_MASK_SOFTWARE | EXC_MASK_BREAKPOINT | + EXC_MASK_RPC_ALERT | EXC_MASK_GUARD); + ExceptionPorts exception_ports(ExceptionPorts::kTargetTypeTask, TASK_NULL); + exception_ports.GetExceptionPorts(mask, &original_handlers_); + exception_ports.SetExceptionPort( + mask, + exception_port_.get(), + EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES, + MACHINE_THREAD_STATE); + + Start(); + } + + // Thread: + + void ThreadMain() override { + UniversalMachExcServer universal_mach_exc_server(this); + while (true) { + mach_msg_return_t mr = + MachMessageServer::Run(&universal_mach_exc_server, + exception_port_.get(), + MACH_MSG_OPTION_NONE, + MachMessageServer::kPersistent, + MachMessageServer::kReceiveLargeIgnore, + kMachMessageTimeoutWaitIndefinitely); + MACH_CHECK(mr == MACH_SEND_INVALID_DEST, mr) << "MachMessageServer::Run"; + } + } + + // UniversalMachExcServer::Interface: + + kern_return_t CatchMachException(exception_behavior_t behavior, + exception_handler_t exception_port, + thread_t thread, + task_t task, + exception_type_t exception, + const mach_exception_data_type_t* code, + mach_msg_type_number_t code_count, + thread_state_flavor_t* flavor, + ConstThreadState old_state, + mach_msg_type_number_t old_state_count, + thread_state_t new_state, + mach_msg_type_number_t* new_state_count, + const mach_msg_trailer_t* trailer, + bool* destroy_complex_request) override { + *destroy_complex_request = true; + + // TODO(justincohen): Forward exceptions to original_handlers_ with + // UniversalExceptionRaise. + + // iOS shouldn't have any child processes, but just in case, those will + // inherit the task exception ports, and this process isn’t prepared to + // handle them + if (task != mach_task_self()) { + LOG(WARNING) << "task 0x" << std::hex << task << " != 0x" + << mach_task_self(); + return KERN_FAILURE; + } + + HandleMachException(behavior, + thread, + exception, + code, + code_count, + *flavor, + old_state, + old_state_count); + + // Respond with KERN_FAILURE so the system will continue to handle this + // exception as a crash. + return KERN_FAILURE; + } + + void HandleMachException(exception_behavior_t behavior, + thread_t thread, + exception_type_t exception, + const mach_exception_data_type_t* code, + mach_msg_type_number_t code_count, + thread_state_flavor_t flavor, + ConstThreadState old_state, + mach_msg_type_number_t old_state_count) { + // TODO(justincohen): This is incomplete. + ProcessSnapshotIOS process_snapshot; + process_snapshot.Initialize(system_data_); + process_snapshot.SetExceptionFromMachException(behavior, + thread, + exception, + code, + code_count, + flavor, + old_state, + old_state_count); + } + + // The signal handler installed at OS-level. + static void CatchSignal(int signo, siginfo_t* siginfo, void* context) { + Get()->HandleAndReraiseSignal( + signo, siginfo, reinterpret_cast(context)); + } + + void HandleAndReraiseSignal(int signo, + siginfo_t* siginfo, + ucontext_t* context) { + // TODO(justincohen): This is incomplete. + ProcessSnapshotIOS process_snapshot; + process_snapshot.Initialize(system_data_); + process_snapshot.SetExceptionFromSignal(siginfo, context); + + // Always call system handler. + Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, &old_action_); + } + + base::mac::ScopedMachReceiveRight exception_port_; + ExceptionPorts::ExceptionHandlerVector original_handlers_; + struct sigaction old_action_ = {}; + IOSSystemDataCollector system_data_; + InitializationStateDcheck initialized_; + + DISALLOW_COPY_AND_ASSIGN(CrashHandler); +}; + +} // namespace + +CrashpadClient::CrashpadClient() {} + +CrashpadClient::~CrashpadClient() {} + +void CrashpadClient::StartCrashpadInProcessHandler() { + InstallObjcExceptionPreprocessor(); + + CrashHandler* crash_handler = CrashHandler::Get(); + DCHECK(crash_handler); + crash_handler->Initialize(); +} + +// static +void CrashpadClient::DumpWithoutCrash(NativeCPUContext* context) { + CrashHandler* crash_handler = CrashHandler::Get(); + DCHECK(crash_handler); + crash_handler->DumpWithoutCrash(context); +} + +} // namespace crashpad diff --git a/client/crashpad_client_ios_test.mm b/client/crashpad_client_ios_test.mm new file mode 100644 index 00000000..002fddbf --- /dev/null +++ b/client/crashpad_client_ios_test.mm @@ -0,0 +1,73 @@ +// Copyright 2020 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "client/crashpad_client.h" + +#import + +#include + +#include "gtest/gtest.h" +#include "testing/platform_test.h" + +namespace crashpad { +namespace test { +namespace { + +using CrashpadIOSClient = PlatformTest; + +TEST_F(CrashpadIOSClient, DumpWithoutCrash) { + CrashpadClient client; + client.StartCrashpadInProcessHandler(); + + NativeCPUContext context; +#if defined(ARCH_CPU_X86_64) + CaptureContext(&context); +#elif defined(ARCH_CPU_ARM64) + // TODO(justincohen): Implement CaptureContext for ARM64. + mach_msg_type_number_t thread_state_count = MACHINE_THREAD_STATE_COUNT; + kern_return_t kr = + thread_get_state(mach_thread_self(), + MACHINE_THREAD_STATE, + reinterpret_cast(&context), + &thread_state_count); + ASSERT_EQ(kr, KERN_SUCCESS); +#endif + client.DumpWithoutCrash(&context); +} + +// This test is covered by a similar XCUITest, but for development purposes +// it's sometimes easier and faster to run as a gtest. However, there's no +// way to correctly run this as a gtest. Leave the test here, disabled, for use +// during development only. +TEST_F(CrashpadIOSClient, DISABLED_ThrowNSException) { + CrashpadClient client; + client.StartCrashpadInProcessHandler(); + [NSException raise:@"GtestNSException" format:@"ThrowException"]; +} + +// This test is covered by a similar XCUITest, but for development purposes +// it's sometimes easier and faster to run as a gtest. However, there's no +// way to correctly run this as a gtest. Leave the test here, disabled, for use +// during development only. +TEST_F(CrashpadIOSClient, DISABLED_ThrowException) { + CrashpadClient client; + client.StartCrashpadInProcessHandler(); + std::vector empty_vector; + empty_vector.at(42); +} + +} // namespace +} // namespace test +} // namespace crashpad diff --git a/client/crashpad_client_linux.cc b/client/crashpad_client_linux.cc index 54fe268a..98c3d092 100644 --- a/client/crashpad_client_linux.cc +++ b/client/crashpad_client_linux.cc @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -25,10 +26,14 @@ #include "base/logging.h" #include "base/strings/stringprintf.h" #include "client/client_argv_handling.h" +#include "third_party/lss/lss.h" #include "util/file/file_io.h" +#include "util/file/filesystem.h" #include "util/linux/exception_handler_client.h" #include "util/linux/exception_information.h" +#include "util/linux/scoped_pr_set_dumpable.h" #include "util/linux/scoped_pr_set_ptracer.h" +#include "util/linux/socket.h" #include "util/misc/from_pointer_cast.h" #include "util/posix/double_fork_and_exec.h" #include "util/posix/signals.h" @@ -41,51 +46,103 @@ 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) { +std::string FormatArgumentAddress(const std::string& name, const void* addr) { return base::StringPrintf("--%s=%p", name.c_str(), addr); } +#if defined(OS_ANDROID) + +std::vector BuildAppProcessArgs( + const std::string& class_name, + const base::FilePath& database, + const base::FilePath& metrics_dir, + const std::string& url, + const std::map& annotations, + const std::vector& arguments, + int socket) { +#if defined(ARCH_CPU_64_BITS) + static constexpr char kAppProcess[] = "/system/bin/app_process64"; +#else + static constexpr char kAppProcess[] = "/system/bin/app_process32"; +#endif + + std::vector argv; + argv.push_back(kAppProcess); + argv.push_back("/system/bin"); + argv.push_back("--application"); + argv.push_back(class_name); + + std::vector handler_argv = + BuildHandlerArgvStrings(base::FilePath(kAppProcess), + database, + metrics_dir, + url, + annotations, + arguments); + + if (socket != kInvalidFileHandle) { + handler_argv.push_back(FormatArgumentInt("initial-client-fd", socket)); + } + + argv.insert(argv.end(), handler_argv.begin(), handler_argv.end()); + return argv; +} + +std::vector BuildArgsToLaunchWithLinker( + const std::string& handler_trampoline, + const std::string& handler_library, + bool is_64_bit, + const base::FilePath& database, + const base::FilePath& metrics_dir, + const std::string& url, + const std::map& annotations, + const std::vector& arguments, + int socket) { + std::vector argv; + if (is_64_bit) { + argv.push_back("/system/bin/linker64"); + } else { + argv.push_back("/system/bin/linker"); + } + argv.push_back(handler_trampoline); + argv.push_back(handler_library); + + std::vector handler_argv = BuildHandlerArgvStrings( + base::FilePath(), database, metrics_dir, url, annotations, arguments); + + if (socket != kInvalidFileHandle) { + handler_argv.push_back(FormatArgumentInt("initial-client-fd", socket)); + } + + argv.insert(argv.end(), handler_argv.begin() + 1, handler_argv.end()); + return argv; +} + +#endif // OS_ANDROID + +// A base class for Crashpad signal handler implementations. class SignalHandler { public: - virtual void HandleCrashFatal(int signo, - siginfo_t* siginfo, - void* context) = 0; - virtual bool HandleCrashNonFatal(int signo, - siginfo_t* siginfo, - void* context) = 0; + // Returns the currently installed signal hander. May be `nullptr` if no + // handler has been installed. + static SignalHandler* Get() { return handler_; } + + // Disables any installed Crashpad signal handler for the calling thread. If a + // crash signal is received, any previously installed (non-Crashpad) signal + // handler will be restored and the signal reraised. + static void DisableForThread() { disabled_for_thread_ = true; } void SetFirstChanceHandler(CrashpadClient::FirstChanceHandler handler) { first_chance_handler_ = handler; } - protected: - SignalHandler() = default; - ~SignalHandler() = default; + // The base implementation for all signal handlers, suitable for calling + // directly to simulate signal delivery. + bool HandleCrash(int signo, siginfo_t* siginfo, void* context) { + if (disabled_for_thread_) { + return false; + } - CrashpadClient::FirstChanceHandler first_chance_handler_ = nullptr; -}; - -// Launches a single use handler to snapshot this process. -class LaunchAtCrashHandler : public SignalHandler { - public: - static LaunchAtCrashHandler* Get() { - static LaunchAtCrashHandler* instance = new LaunchAtCrashHandler(); - return instance; - } - - bool Initialize(std::vector* 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))) { @@ -98,29 +155,100 @@ class LaunchAtCrashHandler : public SignalHandler { exception_information_.context_address = FromPointerCast( context); - exception_information_.thread_id = syscall(SYS_gettid); + exception_information_.thread_id = sys_gettid(); - ScopedPrSetPtracer set_ptracer(getpid(), /* may_log= */ false); + ScopedPrSetDumpable set_dumpable(false); + HandleCrashImpl(); + return false; + } + + protected: + SignalHandler() = default; + + bool Install(const std::set* unhandled_signals) { + DCHECK(!handler_); + handler_ = this; + return Signals::InstallCrashHandlers( + HandleOrReraiseSignal, 0, &old_actions_, unhandled_signals); + } + + const ExceptionInformation& GetExceptionInfo() { + return exception_information_; + } + + virtual void HandleCrashImpl() = 0; + + private: + // The signal handler installed at OS-level. + static void HandleOrReraiseSignal(int signo, + siginfo_t* siginfo, + void* context) { + if (handler_->HandleCrash(signo, siginfo, context)) { + return; + } + Signals::RestoreHandlerAndReraiseSignalOnReturn( + siginfo, handler_->old_actions_.ActionForSignal(signo)); + } + + Signals::OldActions old_actions_ = {}; + ExceptionInformation exception_information_ = {}; + CrashpadClient::FirstChanceHandler first_chance_handler_ = nullptr; + + static SignalHandler* handler_; + + static thread_local bool disabled_for_thread_; + + DISALLOW_COPY_AND_ASSIGN(SignalHandler); +}; +SignalHandler* SignalHandler::handler_ = nullptr; +thread_local bool SignalHandler::disabled_for_thread_ = false; + +// Launches a single use handler to snapshot this process. +class LaunchAtCrashHandler : public SignalHandler { + public: + static LaunchAtCrashHandler* Get() { + static LaunchAtCrashHandler* instance = new LaunchAtCrashHandler(); + return instance; + } + + bool Initialize(std::vector* argv_in, + const std::vector* envp, + const std::set* unhandled_signals) { + argv_strings_.swap(*argv_in); + + if (envp) { + envp_strings_ = *envp; + StringVectorToCStringVector(envp_strings_, &envp_); + set_envp_ = true; + } + + argv_strings_.push_back(FormatArgumentAddress("trace-parent-with-exception", + &GetExceptionInfo())); + + StringVectorToCStringVector(argv_strings_, &argv_); + return Install(unhandled_signals); + } + + void HandleCrashImpl() override { + ScopedPrSetPtracer set_ptracer(sys_getpid(), /* may_log= */ false); pid_t pid = fork(); if (pid < 0) { - return false; + return; } if (pid == 0) { - execv(argv_[0], const_cast(argv_.data())); + if (set_envp_) { + execve(argv_[0], + const_cast(argv_.data()), + const_cast(envp_.data())); + } else { + 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: @@ -128,22 +256,107 @@ class LaunchAtCrashHandler : public SignalHandler { ~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_; + std::vector envp_strings_; + std::vector envp_; + bool set_envp_ = false; DISALLOW_COPY_AND_ASSIGN(LaunchAtCrashHandler); }; -// A pointer to the currently installed crash signal handler. This allows -// the static method CrashpadClient::DumpWithoutCrashing to simulate a crash -// using the currently configured crash handling strategy. -static SignalHandler* g_crash_handler; +class RequestCrashDumpHandler : public SignalHandler { + public: + static RequestCrashDumpHandler* Get() { + static RequestCrashDumpHandler* instance = new RequestCrashDumpHandler(); + return instance; + } + + // pid < 0 indicates the handler pid should be determined by communicating + // over the socket. + // pid == 0 indicates it is not necessary to set the handler as this process' + // ptracer. e.g. if the handler has CAP_SYS_PTRACE or if this process is in a + // user namespace and the handler's uid matches the uid of the process that + // created the namespace. + // pid > 0 directly indicates what the handler's pid is expected to be, so + // retrieving this information from the handler is not necessary. + bool Initialize(ScopedFileHandle sock, + pid_t pid, + const std::set* unhandled_signals) { + ExceptionHandlerClient client(sock.get(), true); + if (pid < 0) { + ucred creds; + if (!client.GetHandlerCredentials(&creds)) { + return false; + } + pid = creds.pid; + } + if (pid > 0 && prctl(PR_SET_PTRACER, pid, 0, 0, 0) != 0) { + PLOG(WARNING) << "prctl"; + // TODO(jperaza): If this call to set the ptracer failed, it might be + // possible to try again just before a dump request, in case the + // environment has changed. Revisit ExceptionHandlerClient::SetPtracer() + // and consider saving the result of this call in ExceptionHandlerClient + // or as a member in this signal handler. ExceptionHandlerClient hasn't + // been responsible for maintaining state and a new ExceptionHandlerClient + // has been constructed as a local whenever a client needs to communicate + // with the handler. ExceptionHandlerClient lifetimes and ownership will + // need to be reconsidered if it becomes responsible for state. + } + sock_to_handler_.reset(sock.release()); + handler_pid_ = pid; + return Install(unhandled_signals); + } + + bool GetHandlerSocket(int* sock, pid_t* pid) { + if (!sock_to_handler_.is_valid()) { + return false; + } + if (sock) { + *sock = sock_to_handler_.get(); + } + if (pid) { + *pid = handler_pid_; + } + return true; + } + + void HandleCrashImpl() override { + ExceptionHandlerProtocol::ClientInformation info = {}; + info.exception_information_address = + FromPointerCast(&GetExceptionInfo()); +#if defined(OS_CHROMEOS) + info.crash_loop_before_time = crash_loop_before_time_; +#endif + + ExceptionHandlerClient client(sock_to_handler_.get(), true); + client.RequestCrashDump(info); + } + +#if defined(OS_CHROMEOS) + void SetCrashLoopBefore(uint64_t crash_loop_before_time) { + crash_loop_before_time_ = crash_loop_before_time; + } +#endif + + private: + RequestCrashDumpHandler() = default; + + ~RequestCrashDumpHandler() = delete; + + ScopedFileHandle sock_to_handler_; + pid_t handler_pid_ = -1; + +#if defined(OS_CHROMEOS) + // An optional UNIX timestamp passed to us from Chrome. + // This will pass to crashpad_handler and then to Chrome OS crash_reporter. + // This should really be a time_t, but it's basically an opaque value (we + // don't anything with it except pass it along). + uint64_t crash_loop_before_time_ = 0; +#endif + + DISALLOW_COPY_AND_ASSIGN(RequestCrashDumpHandler); +}; } // namespace @@ -160,14 +373,134 @@ bool CrashpadClient::StartHandler( 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; + DCHECK(!asynchronous_start); + + ScopedFileHandle client_sock, handler_sock; + if (!UnixCredentialSocket::CreateCredentialSocketpair(&client_sock, + &handler_sock)) { + return false; + } + + std::vector argv = BuildHandlerArgvStrings( + handler, database, metrics_dir, url, annotations, arguments); + + argv.push_back(FormatArgumentInt("initial-client-fd", handler_sock.get())); + argv.push_back("--shared-client-connection"); + if (!DoubleForkAndExec(argv, nullptr, handler_sock.get(), false, nullptr)) { + return false; + } + + pid_t handler_pid = -1; + if (!IsRegularFile(base::FilePath("/proc/sys/kernel/yama/ptrace_scope"))) { + handler_pid = 0; + } + + auto signal_handler = RequestCrashDumpHandler::Get(); + return signal_handler->Initialize( + std::move(client_sock), handler_pid, &unhandled_signals_); +} + +#if defined(OS_ANDROID) || defined(OS_LINUX) +// static +bool CrashpadClient::GetHandlerSocket(int* sock, pid_t* pid) { + auto signal_handler = RequestCrashDumpHandler::Get(); + return signal_handler->GetHandlerSocket(sock, pid); +} + +bool CrashpadClient::SetHandlerSocket(ScopedFileHandle sock, pid_t pid) { + auto signal_handler = RequestCrashDumpHandler::Get(); + return signal_handler->Initialize(std::move(sock), pid, &unhandled_signals_); +} +#endif // OS_ANDROID || OS_LINUX + +#if defined(OS_ANDROID) + +bool CrashpadClient::StartJavaHandlerAtCrash( + const std::string& class_name, + const std::vector* env, + const base::FilePath& database, + const base::FilePath& metrics_dir, + const std::string& url, + const std::map& annotations, + const std::vector& arguments) { + std::vector argv = BuildAppProcessArgs(class_name, + database, + metrics_dir, + url, + annotations, + arguments, + kInvalidFileHandle); + + auto signal_handler = LaunchAtCrashHandler::Get(); + return signal_handler->Initialize(&argv, env, &unhandled_signals_); } // static +bool CrashpadClient::StartJavaHandlerForClient( + const std::string& class_name, + const std::vector* env, + 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 = BuildAppProcessArgs( + class_name, database, metrics_dir, url, annotations, arguments, socket); + return DoubleForkAndExec(argv, env, socket, false, nullptr); +} + +bool CrashpadClient::StartHandlerWithLinkerAtCrash( + const std::string& handler_trampoline, + const std::string& handler_library, + bool is_64_bit, + const std::vector* env, + const base::FilePath& database, + const base::FilePath& metrics_dir, + const std::string& url, + const std::map& annotations, + const std::vector& arguments) { + std::vector argv = + BuildArgsToLaunchWithLinker(handler_trampoline, + handler_library, + is_64_bit, + database, + metrics_dir, + url, + annotations, + arguments, + kInvalidFileHandle); + auto signal_handler = LaunchAtCrashHandler::Get(); + return signal_handler->Initialize(&argv, env, &unhandled_signals_); +} + +// static +bool CrashpadClient::StartHandlerWithLinkerForClient( + const std::string& handler_trampoline, + const std::string& handler_library, + bool is_64_bit, + const std::vector* env, + 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 = + BuildArgsToLaunchWithLinker(handler_trampoline, + handler_library, + is_64_bit, + database, + metrics_dir, + url, + annotations, + arguments, + socket); + return DoubleForkAndExec(argv, env, socket, false, nullptr); +} + +#endif + bool CrashpadClient::StartHandlerAtCrash( const base::FilePath& handler, const base::FilePath& database, @@ -179,12 +512,7 @@ bool CrashpadClient::StartHandlerAtCrash( handler, database, metrics_dir, url, annotations, arguments); auto signal_handler = LaunchAtCrashHandler::Get(); - if (signal_handler->Initialize(&argv)) { - DCHECK(!g_crash_handler); - g_crash_handler = signal_handler; - return true; - } - return false; + return signal_handler->Initialize(&argv, nullptr, &unhandled_signals_); } // static @@ -199,14 +527,17 @@ bool CrashpadClient::StartHandlerForClient( std::vector argv = BuildHandlerArgvStrings( handler, database, metrics_dir, url, annotations, arguments); - argv.push_back(FormatArgumentInt("initial-client", socket)); + argv.push_back(FormatArgumentInt("initial-client-fd", socket)); - return DoubleForkAndExec(argv, socket, true, nullptr); + return DoubleForkAndExec(argv, nullptr, socket, true, nullptr); } // static void CrashpadClient::DumpWithoutCrash(NativeCPUContext* context) { - DCHECK(g_crash_handler); + if (!SignalHandler::Get()) { + DLOG(ERROR) << "Crashpad isn't enabled"; + return; + } #if defined(ARCH_CPU_ARMEL) memset(context->uc_regspace, 0, sizeof(context->uc_regspace)); @@ -220,15 +551,34 @@ void CrashpadClient::DumpWithoutCrash(NativeCPUContext* context) { siginfo.si_signo = Signals::kSimulatedSigno; siginfo.si_errno = 0; siginfo.si_code = 0; - g_crash_handler->HandleCrashNonFatal( + SignalHandler::Get()->HandleCrash( siginfo.si_signo, &siginfo, reinterpret_cast(context)); } +// static +void CrashpadClient::CrashWithoutDump(const std::string& message) { + SignalHandler::DisableForThread(); + LOG(FATAL) << message; +} + // static void CrashpadClient::SetFirstChanceExceptionHandler( FirstChanceHandler handler) { - DCHECK(g_crash_handler); - g_crash_handler->SetFirstChanceHandler(handler); + DCHECK(SignalHandler::Get()); + SignalHandler::Get()->SetFirstChanceHandler(handler); } +void CrashpadClient::SetUnhandledSignals(const std::set& signals) { + DCHECK(!SignalHandler::Get()); + unhandled_signals_ = signals; +} + +#if defined(OS_CHROMEOS) +// static +void CrashpadClient::SetCrashLoopBefore(uint64_t crash_loop_before_time) { + auto request_crash_dump_handler = RequestCrashDumpHandler::Get(); + request_crash_dump_handler->SetCrashLoopBefore(crash_loop_before_time); +} +#endif + } // namespace crashpad diff --git a/client/crashpad_client_linux_test.cc b/client/crashpad_client_linux_test.cc index 4c5bf4de..2bfda5a5 100644 --- a/client/crashpad_client_linux_test.cc +++ b/client/crashpad_client_linux_test.cc @@ -14,8 +14,8 @@ #include "client/crashpad_client.h" +#include #include -#include #include #include #include @@ -37,76 +37,98 @@ #include "util/file/filesystem.h" #include "util/linux/exception_handler_client.h" #include "util/linux/exception_information.h" +#include "util/linux/socket.h" #include "util/misc/address_types.h" #include "util/misc/from_pointer_cast.h" #include "util/posix/signals.h" +#if defined(OS_ANDROID) +#include +#include "dlfcn_internal.h" + +// Normally this comes from set_abort_message.h, but only at API level 21. +extern "C" void android_set_abort_message(const char* msg) + __attribute__((weak)); +#endif + namespace crashpad { namespace test { namespace { +struct StartHandlerForSelfTestOptions { + bool start_handler_at_crash; + bool simulate_crash; + bool set_first_chance_handler; +}; + +class StartHandlerForSelfTest + : public testing::TestWithParam> { + public: + StartHandlerForSelfTest() = default; + ~StartHandlerForSelfTest() = default; + + void SetUp() override { + std::tie(options_.start_handler_at_crash, + options_.simulate_crash, + options_.set_first_chance_handler) = GetParam(); + } + + const StartHandlerForSelfTestOptions& Options() const { return options_; } + + private: + StartHandlerForSelfTestOptions options_; + + DISALLOW_COPY_AND_ASSIGN(StartHandlerForSelfTest); +}; + bool HandleCrashSuccessfully(int, siginfo_t*, ucontext_t*) { return true; } -TEST(CrashpadClient, SimulateCrash) { - ScopedTempDir temp_dir; - - base::FilePath handler_path = TestPaths::Executable().DirName().Append( - FILE_PATH_LITERAL("crashpad_handler")); - - crashpad::CrashpadClient client; - ASSERT_TRUE(client.StartHandlerAtCrash(handler_path, - base::FilePath(temp_dir.path()), - base::FilePath(), - "", - std::map(), - std::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); - } +bool InstallHandler(CrashpadClient* client, + bool start_at_crash, + const base::FilePath& handler_path, + const base::FilePath& database_path) { + return start_at_crash + ? client->StartHandlerAtCrash(handler_path, + database_path, + base::FilePath(), + "", + std::map(), + std::vector()) + : client->StartHandler(handler_path, + database_path, + base::FilePath(), + "", + std::map(), + std::vector(), + false, + false); } constexpr char kTestAnnotationName[] = "name_of_annotation"; constexpr char kTestAnnotationValue[] = "value_of_annotation"; +#if defined(OS_ANDROID) +constexpr char kTestAbortMessage[] = "test abort message"; +#endif + void ValidateDump(const CrashReportDatabase::UploadReport* report) { ProcessSnapshotMinidump minidump_snapshot; ASSERT_TRUE(minidump_snapshot.Initialize(report->Reader())); +#if defined(OS_ANDROID) + // This part of the test requires Q. The API level on Q devices will be 28 + // until the API is finalized, so we can't check API level yet. For now, test + // for the presence of a libc symbol which was introduced in Q. + if (crashpad::internal::Dlsym(RTLD_DEFAULT, "android_fdsan_close_with_tag")) { + const auto& annotations = minidump_snapshot.AnnotationsSimpleMap(); + auto abort_message = annotations.find("abort_message"); + ASSERT_NE(annotations.end(), abort_message); + EXPECT_EQ(kTestAbortMessage, abort_message->second); + } +#endif + for (const ModuleSnapshot* module : minidump_snapshot.Modules()) { for (const AnnotationSnapshot& annotation : module->AnnotationObjects()) { if (static_cast(annotation.type) != @@ -126,7 +148,7 @@ void ValidateDump(const CrashReportDatabase::UploadReport* report) { ADD_FAILURE(); } -CRASHPAD_CHILD_TEST_MAIN(StartHandlerAtCrashChild) { +CRASHPAD_CHILD_TEST_MAIN(StartHandlerForSelfTestChild) { FileHandle in = StdioFileHandle(StdioStream::kStandardInput); VMSize temp_dir_length; @@ -135,6 +157,9 @@ CRASHPAD_CHILD_TEST_MAIN(StartHandlerAtCrashChild) { std::string temp_dir(temp_dir_length, '\0'); CheckedReadFileExactly(in, &temp_dir[0], temp_dir_length); + StartHandlerForSelfTestOptions options; + CheckedReadFileExactly(in, &options, sizeof(options)); + base::FilePath handler_path = TestPaths::Executable().DirName().Append( FILE_PATH_LITERAL("crashpad_handler")); @@ -144,26 +169,41 @@ CRASHPAD_CHILD_TEST_MAIN(StartHandlerAtCrashChild) { test_annotation.Set(kTestAnnotationValue); crashpad::CrashpadClient client; - if (!client.StartHandlerAtCrash(handler_path, - base::FilePath(temp_dir), - base::FilePath(), - "", - std::map(), - std::vector())) { + if (!InstallHandler(&client, + options.start_handler_at_crash, + handler_path, + base::FilePath(temp_dir))) { return EXIT_FAILURE; } +#if defined(OS_ANDROID) + if (android_set_abort_message) { + android_set_abort_message(kTestAbortMessage); + } +#endif + + if (options.simulate_crash) { + if (options.set_first_chance_handler) { + client.SetFirstChanceExceptionHandler(HandleCrashSuccessfully); + } + CRASHPAD_SIMULATE_CRASH(); + return EXIT_SUCCESS; + } + __builtin_trap(); NOTREACHED(); return EXIT_SUCCESS; } -class StartHandlerAtCrashTest : public MultiprocessExec { +class StartHandlerForSelfInChildTest : public MultiprocessExec { public: - StartHandlerAtCrashTest() : MultiprocessExec() { - SetChildTestMainFunction("StartHandlerAtCrashChild"); - SetExpectedChildTerminationBuiltinTrap(); + StartHandlerForSelfInChildTest(const StartHandlerForSelfTestOptions& options) + : MultiprocessExec(), options_(options) { + SetChildTestMainFunction("StartHandlerForSelfTestChild"); + if (!options.simulate_crash) { + SetExpectedChildTerminationBuiltinTrap(); + } } private: @@ -174,6 +214,8 @@ class StartHandlerAtCrashTest : public MultiprocessExec { WritePipeHandle(), &temp_dir_length, sizeof(temp_dir_length))); ASSERT_TRUE(LoggingWriteFile( WritePipeHandle(), temp_dir.path().value().data(), temp_dir_length)); + ASSERT_TRUE( + LoggingWriteFile(WritePipeHandle(), &options_, sizeof(options_))); // Wait for child to finish. CheckedReadFileAtEOF(ReadPipeHandle()); @@ -189,7 +231,11 @@ class StartHandlerAtCrashTest : public MultiprocessExec { reports.clear(); ASSERT_EQ(database->GetPendingReports(&reports), CrashReportDatabase::kNoError); - EXPECT_EQ(reports.size(), 1u); + ASSERT_EQ(reports.size(), options_.set_first_chance_handler ? 0u : 1u); + + if (options_.set_first_chance_handler) { + return; + } std::unique_ptr report; ASSERT_EQ(database->GetReportForUploading(reports[0].uuid, &report), @@ -197,14 +243,26 @@ class StartHandlerAtCrashTest : public MultiprocessExec { ValidateDump(report.get()); } - DISALLOW_COPY_AND_ASSIGN(StartHandlerAtCrashTest); + StartHandlerForSelfTestOptions options_; + + DISALLOW_COPY_AND_ASSIGN(StartHandlerForSelfInChildTest); }; -TEST(CrashpadClient, StartHandlerAtCrash) { - StartHandlerAtCrashTest test; +TEST_P(StartHandlerForSelfTest, StartHandlerInChild) { + if (Options().set_first_chance_handler && !Options().simulate_crash) { + // TODO(jperaza): test first chance handlers with real crashes. + return; + } + StartHandlerForSelfInChildTest test(Options()); test.Run(); } +INSTANTIATE_TEST_SUITE_P(StartHandlerForSelfTestSuite, + StartHandlerForSelfTest, + testing::Combine(testing::Bool(), + testing::Bool(), + testing::Bool())); + // Test state for starting the handler for another process. class StartHandlerForClientTest { public: @@ -213,16 +271,8 @@ class StartHandlerForClientTest { bool Initialize(bool sanitize) { sanitize_ = sanitize; - - int socks[2]; - if (socketpair(AF_UNIX, SOCK_STREAM, 0, socks) != 0) { - PLOG(ERROR) << "socketpair"; - return false; - } - client_sock_.reset(socks[0]); - server_sock_.reset(socks[1]); - - return true; + return UnixCredentialSocket::CreateCredentialSocketpair(&client_sock_, + &server_sock_); } bool StartHandlerOnDemand() { @@ -298,7 +348,7 @@ class StartHandlerForClientTest { static void HandleCrash(int signo, siginfo_t* siginfo, void* context) { auto state = Get(); - char c; + char c = 0; CHECK(LoggingWriteFile(state->client_sock_, &c, sizeof(c))); ExceptionInformation exception_information; @@ -310,7 +360,7 @@ class StartHandlerForClientTest { context); exception_information.thread_id = syscall(SYS_gettid); - ClientInformation info; + ExceptionHandlerProtocol::ClientInformation info; info.exception_information_address = FromPointerCast( &exception_information); @@ -324,7 +374,7 @@ class StartHandlerForClientTest { FromPointerCast(&sanitization_info); } - ExceptionHandlerClient handler_client(state->client_sock_); + ExceptionHandlerClient handler_client(state->client_sock_, false); CHECK_EQ(handler_client.RequestCrashDump(info), 0); Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, nullptr); diff --git a/client/crashpad_client_mac.cc b/client/crashpad_client_mac.cc index 4a408206..2fa8729b 100644 --- a/client/crashpad_client_mac.cc +++ b/client/crashpad_client_mac.cc @@ -26,6 +26,7 @@ #include "base/mac/mach_logging.h" #include "base/strings/stringprintf.h" #include "util/mac/mac_util.h" +#include "util/mach/bootstrap.h" #include "util/mach/child_port_handshake.h" #include "util/mach/exception_ports.h" #include "util/mach/mach_extensions.h" @@ -338,6 +339,7 @@ class HandlerStarter final : public NotifyServer::DefaultInterface { // this interface. if (!DoubleForkAndExec( argv, + nullptr, server_write_fd.get(), true, restart ? CrashpadClient::UseSystemDefaultHandler : nullptr)) { diff --git a/client/crashpad_client_win.cc b/client/crashpad_client_win.cc index 8ceded33..4963f24f 100644 --- a/client/crashpad_client_win.cc +++ b/client/crashpad_client_win.cc @@ -15,6 +15,7 @@ #include "client/crashpad_client.h" #include + #include #include #include @@ -35,10 +36,12 @@ #include "util/misc/random_string.h" #include "util/win/address_types.h" #include "util/win/command_line.h" +#include "util/win/context_wrappers.h" #include "util/win/critical_section_with_debug_info.h" #include "util/win/get_function.h" #include "util/win/handle.h" #include "util/win/initial_client_data.h" +#include "util/win/loader_lock.h" #include "util/win/nt_internals.h" #include "util/win/ntstatus_logging.h" #include "util/win/process_info.h" @@ -73,10 +76,10 @@ base::Lock* g_non_crash_dump_lock; ExceptionInformation g_non_crash_exception_information; enum class StartupState : int { - kNotReady = 0, // This must be value 0 because it is the initial value of a - // global AtomicWord. + kNotReady = 0, // This must be value 0 because it is the initial value of a + // global AtomicWord. kSucceeded = 1, // The CreateProcess() for the handler succeeded. - kFailed = 2, // The handler failed to start. + kFailed = 2, // The handler failed to start. }; // This is a tri-state of type StartupState. It starts at 0 == kNotReady, and @@ -93,9 +96,8 @@ base::subtle::AtomicWord g_handler_startup_state; CRITICAL_SECTION g_critical_section_with_debug_info; void SetHandlerStartupState(StartupState state) { - DCHECK(state == StartupState::kSucceeded || - state == StartupState::kFailed); - base::subtle::Acquire_Store(&g_handler_startup_state, + DCHECK(state == StartupState::kSucceeded || state == StartupState::kFailed); + base::subtle::Release_Store(&g_handler_startup_state, static_cast(state)); } @@ -103,7 +105,7 @@ StartupState BlockUntilHandlerStartedOrFailed() { // Wait until we know the handler has either succeeded or failed to start. base::subtle::AtomicWord startup_state; while ( - (startup_state = base::subtle::Release_Load(&g_handler_startup_state)) == + (startup_state = base::subtle::Acquire_Load(&g_handler_startup_state)) == static_cast(StartupState::kNotReady)) { Sleep(1); } @@ -187,11 +189,7 @@ void HandleAbortSignal(int signum) { EXCEPTION_RECORD record = {}; record.ExceptionCode = STATUS_FATAL_APP_EXIT; record.ExceptionFlags = EXCEPTION_NONCONTINUABLE; -#if defined(ARCH_CPU_64_BITS) - record.ExceptionAddress = reinterpret_cast(context.Rip); -#else - record.ExceptionAddress = reinterpret_cast(context.Eip); -#endif // ARCH_CPU_64_BITS + record.ExceptionAddress = ProgramCounterFromCONTEXT(&context); EXCEPTION_POINTERS exception_pointers; exception_pointers.ContextRecord = &context; @@ -349,6 +347,8 @@ class ScopedCallSetHandlerStartupState { bool StartHandlerProcess( std::unique_ptr data) { + CHECK(!IsThreadInLoaderLock()); + ScopedCallSetHandlerStartupState scoped_startup_state_caller; std::wstring command_line; @@ -476,17 +476,34 @@ bool StartHandlerProcess( } } + // If the embedded crashpad handler is being started via an entry point in a + // DLL (the handler executable is rundll32.exe), then don't pass + // the application name to CreateProcess as this appears to generate an + // invalid command line where the first argument needed by rundll32 is not in + // the correct format as required in: + // https://support.microsoft.com/en-ca/help/164787/info-windows-rundll-and-rundll32-interface + const base::StringPiece16 kRunDll32Exe(L"rundll32.exe"); + bool is_embedded_in_dll = false; + if (data->handler.value().size() >= kRunDll32Exe.size() && + _wcsicmp(data->handler.value() + .substr(data->handler.value().size() - kRunDll32Exe.size()) + .c_str(), + kRunDll32Exe.data()) == 0) { + is_embedded_in_dll = true; + } + PROCESS_INFORMATION process_info; - rv = CreateProcess(data->handler.value().c_str(), - &command_line[0], - nullptr, - nullptr, - true, - creation_flags, - nullptr, - nullptr, - &startup_info.StartupInfo, - &process_info); + rv = CreateProcess( + is_embedded_in_dll ? nullptr : data->handler.value().c_str(), + &command_line[0], + nullptr, + nullptr, + true, + creation_flags, + nullptr, + nullptr, + &startup_info.StartupInfo, + &process_info); if (!rv) { PLOG(ERROR) << "CreateProcess"; return false; @@ -756,11 +773,7 @@ void CrashpadClient::DumpWithoutCrash(const CONTEXT& context) { constexpr uint32_t kSimulatedExceptionCode = 0x517a7ed; EXCEPTION_RECORD record = {}; record.ExceptionCode = kSimulatedExceptionCode; -#if defined(ARCH_CPU_64_BITS) - record.ExceptionAddress = reinterpret_cast(context.Rip); -#else - record.ExceptionAddress = reinterpret_cast(context.Eip); -#endif // ARCH_CPU_64_BITS + record.ExceptionAddress = ProgramCounterFromCONTEXT(&context); exception_pointers.ExceptionRecord = &record; diff --git a/client/crashpad_client_win_test.cc b/client/crashpad_client_win_test.cc index 99778baa..b950699a 100644 --- a/client/crashpad_client_win_test.cc +++ b/client/crashpad_client_win_test.cc @@ -17,7 +17,6 @@ #include #include "base/files/file_path.h" -#include "base/macros.h" #include "base/logging.h" #include "gtest/gtest.h" #include "test/test_paths.h" diff --git a/client/crashpad_info.h b/client/crashpad_info.h index 5db9a6cd..ed7b9c14 100644 --- a/client/crashpad_info.h +++ b/client/crashpad_info.h @@ -141,15 +141,15 @@ struct CrashpadInfo { //! //! When handling an exception, the Crashpad handler will scan all modules in //! a process. The first one that has a CrashpadInfo structure populated with - //! a value other than #kUnset for this field will dictate whether the handler - //! is functional or not. If all modules with a CrashpadInfo structure specify - //! #kUnset, the handler will be enabled. If disabled, the Crashpad handler - //! will still run and receive exceptions, but will not take any action on an - //! exception on its own behalf, except for the action necessary to determine - //! that it has been disabled. + //! a value other than TriState::kUnset for this field will dictate whether + //! the handler is functional or not. If all modules with a CrashpadInfo + //! structure specify TriState::kUnset, the handler will be enabled. If + //! disabled, the Crashpad handler will still run and receive exceptions, but + //! will not take any action on an exception on its own behalf, except for the + //! action necessary to determine that it has been disabled. //! - //! The Crashpad handler should not normally be disabled. More commonly, it - //! is appropriate to disable crash report upload by calling + //! The Crashpad handler should not normally be disabled. More commonly, it is + //! appropriate to disable crash report upload by calling //! Settings::SetUploadsEnabled(). void set_crashpad_handler_behavior(TriState crashpad_handler_behavior) { crashpad_handler_behavior_ = crashpad_handler_behavior; @@ -160,15 +160,15 @@ struct CrashpadInfo { //! //! When handling an exception, the Crashpad handler will scan all modules in //! a process. The first one that has a CrashpadInfo structure populated with - //! a value other than #kUnset for this field will dictate whether the - //! exception is forwarded to the system’s crash reporter. If all modules with - //! a CrashpadInfo structure specify #kUnset, forwarding will be enabled. - //! Unless disabled, forwarding may still occur if the Crashpad handler is - //! disabled by SetCrashpadHandlerState(). Even when forwarding is enabled, - //! the Crashpad handler may choose not to forward all exceptions to the - //! system’s crash reporter in cases where it has reason to believe that the - //! system’s crash reporter would not normally have handled the exception in - //! Crashpad’s absence. + //! a value other than TriState::kUnset for this field will dictate whether + //! the exception is forwarded to the system’s crash reporter. If all modules + //! with a CrashpadInfo structure specify TriState::kUnset, forwarding will be + //! enabled. Unless disabled, forwarding may still occur if the Crashpad + //! handler is disabled by SetCrashpadHandlerState(). Even when forwarding is + //! enabled, the Crashpad handler may choose not to forward all exceptions to + //! the system’s crash reporter in cases where it has reason to believe that + //! the system’s crash reporter would not normally have handled the exception + //! in Crashpad’s absence. void set_system_crash_reporter_forwarding( TriState system_crash_reporter_forwarding) { system_crash_reporter_forwarding_ = system_crash_reporter_forwarding; @@ -179,8 +179,8 @@ struct CrashpadInfo { //! //! When handling an exception, the Crashpad handler will scan all modules in //! a process. The first one that has a CrashpadInfo structure populated with - //! a value other than #kUnset for this field will dictate whether the extra - //! memory is captured. + //! a value other than TriState::kUnset for this field will dictate whether + //! the extra memory is captured. //! //! This causes Crashpad to include pages of data referenced by locals or //! other stack memory. Turning this on can increase the size of the minidump @@ -208,7 +208,7 @@ struct CrashpadInfo { //! Note that streams will appear in the minidump in the reverse order to //! which they are added. //! - //! TODO(scottmg) This is currently only supported on Windows. + //! TODO(scottmg) This is currently not supported on Mac. //! //! \param[in] stream_type The stream type identifier to use. This should be //! normally be larger than `MINIDUMP_STREAM_TYPE::LastReservedStream` diff --git a/client/crashpad_info_note.S b/client/crashpad_info_note.S index 4c29298d..b13d8642 100644 --- a/client/crashpad_info_note.S +++ b/client/crashpad_info_note.S @@ -26,16 +26,13 @@ #define NOTE_ALIGN 4 // This section must be "a"llocated so that it appears in the final binary at - // runtime, and "w"ritable so that the relocation to CRASHPAD_INFO_SYMBOL can - // be performed. - .section .note.crashpad.info,"aw",%note + // runtime. The reference to CRASHPAD_INFO_SYMBOL uses an offset relative to + // this note to avoid making this note writable, which triggers a bug in GNU + // ld, or adding text relocations which require the target system to allow + // making text segments writable. https://crbug.com/crashpad/260. + .section .note.crashpad.info,"a",%note .balign NOTE_ALIGN - # .globl indicates that it's available to link against other .o files. .hidden - # indicates that it will not appear in the executable's symbol table. - .globl CRASHPAD_NOTE_REFERENCE - .hidden CRASHPAD_NOTE_REFERENCE - .type CRASHPAD_NOTE_REFERENCE, %object -CRASHPAD_NOTE_REFERENCE: +CRASHPAD_NOTE: .long name_end - name // namesz .long desc_end - desc // descsz .long CRASHPAD_ELF_NOTE_TYPE_CRASHPAD_INFO // type @@ -45,15 +42,26 @@ name_end: .balign NOTE_ALIGN desc: #if defined(__LP64__) - .quad CRASHPAD_INFO_SYMBOL + .quad CRASHPAD_INFO_SYMBOL - desc #else -#if defined(__LITTLE_ENDIAN__) - .long CRASHPAD_INFO_SYMBOL - .long 0 -#else - .long 0 - .long CRASHPAD_INFO_SYMBOL -#endif // __LITTLE_ENDIAN__ + .long CRASHPAD_INFO_SYMBOL - desc #endif // __LP64__ desc_end: - .size CRASHPAD_NOTE_REFERENCE, .-CRASHPAD_NOTE_REFERENCE + .size CRASHPAD_NOTE, .-CRASHPAD_NOTE + + // CRASHPAD_NOTE can't be referenced directly by GetCrashpadInfo() because the + // relocation used to make the reference may require that the address be + // 8-byte aligned and notes must have 4-byte alignment. + .section .rodata,"a",%progbits + .balign 8 + # .globl indicates that it's available to link against other .o files. .hidden + # indicates that it will not appear in the executable's symbol table. + .globl CRASHPAD_NOTE_REFERENCE + .hidden CRASHPAD_NOTE_REFERENCE + .type CRASHPAD_NOTE_REFERENCE, %object +CRASHPAD_NOTE_REFERENCE: + // The value of this quad isn't important. It exists to reference + // CRASHPAD_NOTE, causing the linker to include the note into the binary + // linking Crashpad. The subtraction from |name| is a convenience to allow the + // value to be computed statically. + .quad name - CRASHPAD_NOTE diff --git a/client/prune_crash_reports.cc b/client/prune_crash_reports.cc index d045eb6a..8eed18d4 100644 --- a/client/prune_crash_reports.cc +++ b/client/prune_crash_reports.cc @@ -15,6 +15,7 @@ #include "client/prune_crash_reports.h" #include +#include #include #include @@ -24,7 +25,7 @@ namespace crashpad { -void PruneCrashReportDatabase(CrashReportDatabase* database, +size_t PruneCrashReportDatabase(CrashReportDatabase* database, PruneCondition* condition) { std::vector all_reports; CrashReportDatabase::OperationStatus status; @@ -32,14 +33,14 @@ void PruneCrashReportDatabase(CrashReportDatabase* database, status = database->GetPendingReports(&all_reports); if (status != CrashReportDatabase::kNoError) { LOG(ERROR) << "PruneCrashReportDatabase: Failed to get pending reports"; - return; + return 0; } std::vector completed_reports; status = database->GetCompletedReports(&completed_reports); if (status != CrashReportDatabase::kNoError) { LOG(ERROR) << "PruneCrashReportDatabase: Failed to get completed reports"; - return; + return 0; } all_reports.insert(all_reports.end(), completed_reports.begin(), completed_reports.end()); @@ -50,16 +51,21 @@ void PruneCrashReportDatabase(CrashReportDatabase* database, return lhs.creation_time > rhs.creation_time; }); + size_t num_pruned = 0; for (const auto& report : all_reports) { if (condition->ShouldPruneReport(report)) { status = database->DeleteReport(report.uuid); if (status != CrashReportDatabase::kNoError) { LOG(ERROR) << "Database Pruning: Failed to remove report " << report.uuid.ToString(); + } else { + num_pruned++; } } } + return num_pruned; + // TODO(rsesek): For databases that do not use a directory structure, it is // possible for the metadata sidecar to become corrupted and thus leave // orphaned crash report files on-disk. https://crashpad.chromium.org/bug/66 @@ -96,19 +102,9 @@ DatabaseSizePruneCondition::~DatabaseSizePruneCondition() {} bool DatabaseSizePruneCondition::ShouldPruneReport( const CrashReportDatabase::Report& report) { -#if defined(OS_POSIX) - struct stat statbuf; - if (stat(report.file_path.value().c_str(), &statbuf) == 0) { -#elif defined(OS_WIN) - struct _stati64 statbuf; - if (_wstat64(report.file_path.value().c_str(), &statbuf) == 0) { -#else -#error "Not implemented" -#endif - // Round up fractional KB to the next 1-KB boundary. - measured_size_in_kb_ += - static_cast((statbuf.st_size + 1023) / 1024); - } + // Round up fractional KB to the next 1-KB boundary. + measured_size_in_kb_ += + static_cast((report.total_size + 1023) / 1024); return measured_size_in_kb_ > max_size_in_kb_; } diff --git a/client/prune_crash_reports.h b/client/prune_crash_reports.h index 6dac5f30..07a70980 100644 --- a/client/prune_crash_reports.h +++ b/client/prune_crash_reports.h @@ -37,8 +37,10 @@ class PruneCondition; //! \param[in] database The database from which crash reports will be deleted. //! \param[in] condition The condition against which all reports in the database //! will be evaluated. -void PruneCrashReportDatabase(CrashReportDatabase* database, - PruneCondition* condition); +//! +//! \return The number of deleted crash reports. +size_t PruneCrashReportDatabase(CrashReportDatabase* database, + PruneCondition* condition); std::unique_ptr GetDefaultDatabasePruneCondition(); diff --git a/client/prune_crash_reports_test.cc b/client/prune_crash_reports_test.cc index 01990d27..dbe8c0a5 100644 --- a/client/prune_crash_reports_test.cc +++ b/client/prune_crash_reports_test.cc @@ -15,14 +15,15 @@ #include "client/prune_crash_reports.h" #include +#include #include #include +#include #include #include #include "base/numerics/safe_conversions.h" -#include "base/rand_util.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include "test/scoped_temp_dir.h" @@ -81,56 +82,49 @@ TEST(PruneCrashReports, AgeCondition) { } TEST(PruneCrashReports, SizeCondition) { - ScopedTempDir temp_dir; - CrashReportDatabase::Report report_1k; - report_1k.file_path = temp_dir.path().Append(FILE_PATH_LITERAL("file1024")); + report_1k.total_size = 1024u; CrashReportDatabase::Report report_3k; - report_3k.file_path = temp_dir.path().Append(FILE_PATH_LITERAL("file3072")); + report_3k.total_size = 1024u * 3u; + CrashReportDatabase::Report report_unset_size; { - ScopedFileHandle scoped_file_1k( - LoggingOpenFileForWrite(report_1k.file_path, - FileWriteMode::kCreateOrFail, - FilePermissions::kOwnerOnly)); - ASSERT_TRUE(scoped_file_1k.is_valid()); - - std::string string; - for (int i = 0; i < 128; ++i) - string.push_back(static_cast(i)); - - for (size_t i = 0; i < 1024; i += string.size()) { - ASSERT_TRUE(LoggingWriteFile(scoped_file_1k.get(), - string.c_str(), string.length())); - } - - ScopedFileHandle scoped_file_3k( - LoggingOpenFileForWrite(report_3k.file_path, - FileWriteMode::kCreateOrFail, - FilePermissions::kOwnerOnly)); - ASSERT_TRUE(scoped_file_3k.is_valid()); - - for (size_t i = 0; i < 3072; i += string.size()) { - ASSERT_TRUE(LoggingWriteFile(scoped_file_3k.get(), - string.c_str(), string.length())); - } - } - - { - DatabaseSizePruneCondition condition(1); + DatabaseSizePruneCondition condition(/*max_size_in_kb=*/1); + // |report_1k| should not be pruned as the cumulated size is not past 1kB + // yet. EXPECT_FALSE(condition.ShouldPruneReport(report_1k)); - EXPECT_TRUE(condition.ShouldPruneReport(report_1k)); - } - - { - DatabaseSizePruneCondition condition(1); + // |report_3k| should be pruned as the cumulated size is now past 1kB. EXPECT_TRUE(condition.ShouldPruneReport(report_3k)); } { - DatabaseSizePruneCondition condition(6); + DatabaseSizePruneCondition condition(/*max_size_in_kb=*/1); + // |report_3k| should be pruned as the cumulated size is already past 1kB. + EXPECT_TRUE(condition.ShouldPruneReport(report_3k)); + } + + { + DatabaseSizePruneCondition condition(/*max_size_in_kb=*/6); + // |report_3k| should not be pruned as the cumulated size is not past 6kB + // yet. EXPECT_FALSE(condition.ShouldPruneReport(report_3k)); + // |report_3k| should not be pruned as the cumulated size is not past 6kB + // yet. EXPECT_FALSE(condition.ShouldPruneReport(report_3k)); + // |report_1k| should be pruned as the cumulated size is now past 6kB. + EXPECT_TRUE(condition.ShouldPruneReport(report_1k)); + } + + { + DatabaseSizePruneCondition condition(/*max_size_in_kb=*/0); + // |report_unset_size| should not be pruned as its size is 0, regardless of + // how many times we try to prune it. + EXPECT_FALSE(condition.ShouldPruneReport(report_unset_size)); + EXPECT_FALSE(condition.ShouldPruneReport(report_unset_size)); + EXPECT_FALSE(condition.ShouldPruneReport(report_unset_size)); + EXPECT_FALSE(condition.ShouldPruneReport(report_unset_size)); + EXPECT_FALSE(condition.ShouldPruneReport(report_unset_size)); + // |report_1k| should be pruned as the cumulated size is now past 0kB. EXPECT_TRUE(condition.ShouldPruneReport(report_1k)); } } @@ -211,18 +205,16 @@ TEST(PruneCrashReports, PruneOrder) { using ::testing::Return; using ::testing::SetArgPointee; + const size_t kNumReports = 10; std::vector reports; - for (int i = 0; i < 10; ++i) { + for (size_t i = 0; i < kNumReports; ++i) { CrashReportDatabase::Report temp; - temp.uuid.data_1 = i; - temp.creation_time = NDaysAgo(i * 10); + temp.uuid.data_1 = static_cast(i); + temp.creation_time = NDaysAgo(static_cast(i) * 10); reports.push_back(temp); } - // The randomness from std::rand() is not, so use a better rand() instead. - const auto random_generator = [](ptrdiff_t rand_max) { - return base::RandInt(0, base::checked_cast(rand_max) - 1); - }; - std::random_shuffle(reports.begin(), reports.end(), random_generator); + std::mt19937 urng(std::random_device{}()); + std::shuffle(reports.begin(), reports.end(), urng); std::vector pending_reports( reports.begin(), reports.begin() + 5); std::vector completed_reports( @@ -241,7 +233,7 @@ TEST(PruneCrashReports, PruneOrder) { } StaticCondition delete_all(true); - PruneCrashReportDatabase(&db, &delete_all); + EXPECT_EQ(PruneCrashReportDatabase(&db, &delete_all), kNumReports); } } // namespace diff --git a/client/settings.cc b/client/settings.cc index 20bd2581..0aa525f1 100644 --- a/client/settings.cc +++ b/client/settings.cc @@ -63,7 +63,8 @@ void Settings::ScopedLockedFileHandle::Destroy() { CheckedCloseFile(handle_); } if (!lockfile_path_.empty()) { - DCHECK(LoggingRemoveFile(lockfile_path_)); + const bool success = LoggingRemoveFile(lockfile_path_); + DCHECK(success); } } @@ -84,8 +85,8 @@ void ScopedLockedFileHandleTraits::Free(FileHandle handle) { #endif // OS_FUCHSIA struct Settings::Data { - static const uint32_t kSettingsMagic = 'CPds'; - static const uint32_t kSettingsVersion = 1; + static constexpr uint32_t kSettingsMagic = 'CPds'; + static constexpr uint32_t kSettingsVersion = 1; enum Options : uint32_t { kUploadsEnabled = 1 << 0, @@ -225,10 +226,10 @@ Settings::ScopedLockedFileHandle Settings::OpenForReadingAndWriting( FileHandle handle; if (log_open_error) { handle = LoggingOpenFileForReadAndWrite( - file_path(), mode, FilePermissions::kWorldReadable); + file_path(), mode, FilePermissions::kOwnerOnly); } else { handle = OpenFileForReadAndWrite( - file_path(), mode, FilePermissions::kWorldReadable); + file_path(), mode, FilePermissions::kOwnerOnly); } return MakeScopedLockedFileHandle( diff --git a/client/settings.h b/client/settings.h index a2b0c746..5761c6b9 100644 --- a/client/settings.h +++ b/client/settings.h @@ -75,6 +75,10 @@ class Settings { //! //! The default value is `false`. //! + //! \note + //! This setting is ignored if --use-cros-crash-reporter is present + //! (which it will be if invoked by Chrome on ChromeOS). + //! //! \param[out] enabled Whether crash reports should be uploaded. //! //! \return On success, returns `true`, otherwise returns `false` with an diff --git a/client/simple_string_dictionary_test.cc b/client/simple_string_dictionary_test.cc index 7af51a27..cd23216c 100644 --- a/client/simple_string_dictionary_test.cc +++ b/client/simple_string_dictionary_test.cc @@ -73,7 +73,7 @@ TEST(SimpleStringDictionary, SimpleStringDictionary) { EXPECT_FALSE(dict.GetValueForKey("key3")); // Remove by setting value to nullptr - dict.SetKeyValue("key2", nullptr); + dict.SetKeyValue("key2", base::StringPiece(nullptr, 0)); // Now make sure it's not there anymore EXPECT_FALSE(dict.GetValueForKey("key2")); @@ -254,13 +254,14 @@ TEST(SimpleStringDictionary, OutOfSpace) { TEST(SimpleStringDictionaryDeathTest, SetKeyValueWithNullKey) { TSimpleStringDictionary<4, 6, 6> map; - ASSERT_DEATH_CHECK(map.SetKeyValue(nullptr, "hello"), "key"); + ASSERT_DEATH_CHECK(map.SetKeyValue(base::StringPiece(nullptr, 0), "hello"), + "key"); } TEST(SimpleStringDictionaryDeathTest, GetValueForKeyWithNullKey) { TSimpleStringDictionary<4, 6, 6> map; map.SetKeyValue("hi", "there"); - ASSERT_DEATH_CHECK(map.GetValueForKey(nullptr), "key"); + ASSERT_DEATH_CHECK(map.GetValueForKey(base::StringPiece(nullptr, 0)), "key"); EXPECT_STREQ("there", map.GetValueForKey("hi")); } diff --git a/client/simulate_crash_mac.cc b/client/simulate_crash_mac.cc index 1e2d8e42..43d05b81 100644 --- a/client/simulate_crash_mac.cc +++ b/client/simulate_crash_mac.cc @@ -20,7 +20,7 @@ #include "base/logging.h" #include "base/mac/mach_logging.h" #include "base/mac/scoped_mach_port.h" -#include "base/macros.h" +#include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "build/build_config.h" #include "util/mach/exc_client_variants.h" @@ -191,7 +191,7 @@ void SimulateCrash(const NativeCPUContext& cpu_context) { base::mac::ScopedMachSendRight thread(mach_thread_self()); exception_type_t exception = kMachExceptionSimulated; mach_exception_data_type_t codes[] = {0, 0}; - mach_msg_type_number_t code_count = arraysize(codes); + mach_msg_type_number_t code_count = base::size(codes); // Look up the handler for EXC_CRASH exceptions in the same way that the // kernel would: try a thread handler, then a task handler, and finally a host @@ -213,7 +213,7 @@ void SimulateCrash(const NativeCPUContext& cpu_context) { bool success = false; for (size_t target_type_index = 0; - !success && target_type_index < arraysize(kTargetTypes); + !success && target_type_index < base::size(kTargetTypes); ++target_type_index) { ExceptionPorts::ExceptionHandlerVector handlers; ExceptionPorts exception_ports(kTargetTypes[target_type_index], diff --git a/client/simulate_crash_mac_test.cc b/client/simulate_crash_mac_test.cc index 1d63ff65..c2f4c204 100644 --- a/client/simulate_crash_mac_test.cc +++ b/client/simulate_crash_mac_test.cc @@ -19,6 +19,7 @@ #include #include "base/macros.h" +#include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "build/build_config.h" #include "gtest/gtest.h" @@ -342,15 +343,13 @@ TEST(SimulateCrash, SimulateCrash) { #endif }; - for (size_t target_index = 0; - target_index < arraysize(kTargets); + for (size_t target_index = 0; target_index < base::size(kTargets); ++target_index) { TestSimulateCrashMac::ExceptionPortsTarget target = kTargets[target_index]; SCOPED_TRACE(base::StringPrintf( "target_index %zu, target %d", target_index, target)); - for (size_t behavior_index = 0; - behavior_index < arraysize(kBehaviors); + for (size_t behavior_index = 0; behavior_index < base::size(kBehaviors); ++behavior_index) { exception_behavior_t behavior = kBehaviors[behavior_index]; SCOPED_TRACE(base::StringPrintf( @@ -364,8 +363,7 @@ TEST(SimulateCrash, SimulateCrash) { target, behavior, THREAD_STATE_NONE); test_simulate_crash_mac.Run(); } else { - for (size_t flavor_index = 0; - flavor_index < arraysize(kFlavors); + for (size_t flavor_index = 0; flavor_index < base::size(kFlavors); ++flavor_index) { thread_state_flavor_t flavor = kFlavors[flavor_index]; SCOPED_TRACE(base::StringPrintf( diff --git a/codereview.settings b/codereview.settings index d8630972..976f8e53 100644 --- a/codereview.settings +++ b/codereview.settings @@ -13,7 +13,6 @@ # limitations under the License. GERRIT_HOST: True -GERRIT_SQUASH_UPLOADS: True CODE_REVIEW_SERVER: https://chromium-review.googlesource.com/ VIEW_VC: https://chromium.googlesource.com/crashpad/crashpad/+/ PROJECT: crashpad diff --git a/compat/BUILD.gn b/compat/BUILD.gn index 2bfe074f..9c22acfc 100644 --- a/compat/BUILD.gn +++ b/compat/BUILD.gn @@ -17,8 +17,14 @@ import("../build/crashpad_buildconfig.gni") config("compat_config") { include_dirs = [] - if (crashpad_is_mac) { + if (crashpad_is_mac || crashpad_is_ios) { include_dirs += [ "mac" ] + } else { + include_dirs += [ "non_mac" ] + } + + if (crashpad_is_ios) { + include_dirs += [ "ios" ] } if (crashpad_is_linux || crashpad_is_android) { @@ -34,10 +40,14 @@ config("compat_config") { } else { include_dirs += [ "non_win" ] } + + if (!crashpad_is_linux && !crashpad_is_android && !crashpad_is_fuchsia) { + include_dirs += [ "non_elf" ] + } } template("compat_target") { - if (crashpad_is_mac) { + if (crashpad_is_mac || crashpad_is_ios) { # There are no sources to compile, which doesn’t mix will with a # static_library. group(target_name) { @@ -53,21 +63,39 @@ template("compat_target") { compat_target("compat") { sources = [] - if (crashpad_is_mac) { + if (crashpad_is_mac || crashpad_is_ios) { sources += [ "mac/AvailabilityMacros.h", "mac/kern/exc_resource.h", "mac/mach-o/loader.h", + "mac/mach/i386/thread_state.h", "mac/mach/mach.h", "mac/sys/resource.h", ] } else { - sources += [ "non_mac/mach/mach.h" ] + sources += [ + "non_mac/mach-o/loader.h", + "non_mac/mach/mach.h", + "non_mac/mach/machine.h", + "non_mac/mach/vm_prot.h", + ] + } + + if (crashpad_is_ios) { + sources += [ + "ios/mach/exc.defs", + "ios/mach/mach_exc.defs", + "ios/mach/mach_types.defs", + "ios/mach/machine/machine_types.defs", + "ios/mach/std_types.defs", + ] } if (crashpad_is_linux || crashpad_is_android) { sources += [ "linux/signal.h", + "linux/sys/mman.cc", + "linux/sys/mman.h", "linux/sys/ptrace.h", "linux/sys/user.h", ] @@ -75,6 +103,8 @@ compat_target("compat") { if (crashpad_is_android) { sources += [ + "android/android/api-level.cc", + "android/android/api-level.h", "android/dlfcn_internal.cc", "android/dlfcn_internal.h", "android/elf.h", @@ -114,6 +144,10 @@ compat_target("compat") { ] } + if (!crashpad_is_linux && !crashpad_is_android && !crashpad_is_fuchsia) { + sources += [ "non_elf/elf.h" ] + } + public_configs = [ ":compat_config", "..:crashpad_config", @@ -121,7 +155,15 @@ compat_target("compat") { deps = [] + if (!crashpad_is_mac) { + deps += [ "../third_party/xnu" ] + } + if (crashpad_is_win) { deps += [ "../third_party/getopt" ] } + + if (!crashpad_is_linux && !crashpad_is_android && !crashpad_is_fuchsia) { + deps += [ "../third_party/glibc" ] + } } diff --git a/compat/android/android/api-level.cc b/compat/android/android/api-level.cc new file mode 100644 index 00000000..c29ec1a0 --- /dev/null +++ b/compat/android/android/api-level.cc @@ -0,0 +1,50 @@ +// Copyright 2018 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include +#include +#include +#include + +#include "dlfcn_internal.h" + +#if __NDK_MAJOR__ < 20 + +extern "C" { + +int android_get_device_api_level() { + using FuncType = int (*)(); + static const FuncType bionic_get_device_api_level = + reinterpret_cast( + crashpad::internal::Dlsym(RTLD_NEXT, "android_get_device_api_level")); + + if (bionic_get_device_api_level) { + return bionic_get_device_api_level(); + } + + char api_string[PROP_VALUE_MAX]; + int length = __system_property_get("ro.build.version.sdk", api_string); + if (length <= 0) { + return -1; + } + + int api_level = atoi(api_string); + return api_level > 0 ? api_level : -1; +} + +} // extern "C" + +#endif // __NDK_MAJOR__ < 20 diff --git a/compat/android/android/api-level.h b/compat/android/android/api-level.h new file mode 100644 index 00000000..bfff9af5 --- /dev/null +++ b/compat/android/android/api-level.h @@ -0,0 +1,39 @@ +// Copyright 2018 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_COMPAT_ANDROID_ANDROID_API_LEVEL_H_ +#define CRASHPAD_COMPAT_ANDROID_ANDROID_API_LEVEL_H_ + +#include_next +#include + +#include + +#if __NDK_MAJOR__ < 20 + +#ifdef __cplusplus +extern "C" { +#endif + +// Returns the API level of the device or -1 if it can't be determined. This +// function is provided by NDK r20. +int android_get_device_api_level(); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // __NDK_MAJOR__ < 20 + +#endif // CRASHPAD_COMPAT_ANDROID_ANDROID_API_LEVEL_H_ diff --git a/compat/android/sys/mman.cc b/compat/android/sys/mman.cc index 4c29a9df..d890d84d 100644 --- a/compat/android/sys/mman.cc +++ b/compat/android/sys/mman.cc @@ -35,7 +35,7 @@ extern "C" void* __mmap2(void* addr, namespace { template -T Align(T value, uint8_t alignment) { +T Align(T value, size_t alignment) { return (value + alignment - 1) & ~(alignment - 1); } diff --git a/compat/compat.gyp b/compat/compat.gyp index 1229ee0c..6294d1fc 100644 --- a/compat/compat.gyp +++ b/compat/compat.gyp @@ -20,6 +20,8 @@ { 'target_name': 'crashpad_compat', 'sources': [ + 'android/android/api-level.cc', + 'android/android/api-level.h', 'android/dlfcn_internal.cc', 'android/dlfcn_internal.h', 'android/elf.h', diff --git a/compat/ios/mach/exc.defs b/compat/ios/mach/exc.defs new file mode 100644 index 00000000..d1648e97 --- /dev/null +++ b/compat/ios/mach/exc.defs @@ -0,0 +1,20 @@ +// Copyright 2020 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_COMPAT_IOS_MACH_EXC_DEFS_ +#define CRASHPAD_COMPAT_IOS_MACH_EXC_DEFS_ + +#include "third_party/xnu/osfmk/mach/exc.defs" + +#endif // CRASHPAD_COMPAT_IOS_MACH_EXC_DEFS_ diff --git a/compat/ios/mach/mach_exc.defs b/compat/ios/mach/mach_exc.defs new file mode 100644 index 00000000..c562128e --- /dev/null +++ b/compat/ios/mach/mach_exc.defs @@ -0,0 +1,20 @@ +// Copyright 2020 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_COMPAT_IOS_MACH_MACH_EXC_DEFS_ +#define CRASHPAD_COMPAT_IOS_MACH_MACH_EXC_DEFS_ + +#include "third_party/xnu/osfmk/mach/mach_exc.defs" + +#endif // CRASHPAD_COMPAT_IOS_MACH_MACH_EXC_DEFS_ diff --git a/compat/ios/mach/mach_types.defs b/compat/ios/mach/mach_types.defs new file mode 100644 index 00000000..dc18b8eb --- /dev/null +++ b/compat/ios/mach/mach_types.defs @@ -0,0 +1,20 @@ +// Copyright 2020 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_COMPAT_IOS_MACH_MACH_TYPES_DEFS_ +#define CRASHPAD_COMPAT_IOS_MACH_MACH_TYPES_DEFS_ + +#include "third_party/xnu/osfmk/mach/mach_types.defs" + +#endif // CRASHPAD_COMPAT_IOS_MACH_MACH_TYPES_DEFS_ diff --git a/util/misc/arraysize_unsafe.h b/compat/ios/mach/machine/machine_types.defs similarity index 52% rename from util/misc/arraysize_unsafe.h rename to compat/ios/mach/machine/machine_types.defs index e53c70b8..e906466f 100644 --- a/util/misc/arraysize_unsafe.h +++ b/compat/ios/mach/machine/machine_types.defs @@ -1,4 +1,4 @@ -// Copyright 2016 The Crashpad Authors. All rights reserved. +// Copyright 2020 The Crashpad Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,16 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef CRASHPAD_UTIL_MISC_ARRAYSIZE_UNSAFE_H_ -#define CRASHPAD_UTIL_MISC_ARRAYSIZE_UNSAFE_H_ +#ifndef CRASHPAD_COMPAT_IOS_MACH_MACHINE_MACHINE_TYPES_DEFS_ +#define CRASHPAD_COMPAT_IOS_MACH_MACHINE_MACHINE_TYPES_DEFS_ -//! \file +#include "third_party/xnu/osfmk/mach/machine/machine_types.defs" -//! \brief Not the safest way of computing an array’s size… -//! -//! `#%include "base/macros.h"` and use its `arraysize()` instead. This macro -//! should only be used in rare situations where `arraysize()` does not -//! function. -#define ARRAYSIZE_UNSAFE(array) (sizeof(array) / sizeof(array[0])) - -#endif // CRASHPAD_UTIL_MISC_ARRAYSIZE_UNSAFE_H_ +#endif // CRASHPAD_COMPAT_IOS_MACH_MACHINE_MACHINE_TYPES_DEFS_ diff --git a/compat/ios/mach/std_types.defs b/compat/ios/mach/std_types.defs new file mode 100644 index 00000000..e49c6e46 --- /dev/null +++ b/compat/ios/mach/std_types.defs @@ -0,0 +1,20 @@ +// Copyright 2020 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_COMPAT_IOS_MACH_STD_TYPES_DEFS_ +#define CRASHPAD_COMPAT_IOS_MACH_STD_TYPES_DEFS_ + +#include "third_party/xnu/osfmk/mach/std_types.defs" + +#endif // CRASHPAD_COMPAT_IOS_MACH_STD_TYPES_DEFS_ diff --git a/compat/linux/sys/mman.cc b/compat/linux/sys/mman.cc new file mode 100644 index 00000000..12aaa2c7 --- /dev/null +++ b/compat/linux/sys/mman.cc @@ -0,0 +1,35 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include +#include +#include + +#if defined(__GLIBC__) + +extern "C" { + +int memfd_create(const char* name, unsigned int flags) { + using MemfdCreateType = int (*)(const char*, int); + static const MemfdCreateType next_memfd_create = + reinterpret_cast(dlsym(RTLD_NEXT, "memfd_create")); + return next_memfd_create ? next_memfd_create(name, flags) + : syscall(SYS_memfd_create, name, flags); +} + +} // extern "C" + +#endif // __GLIBC__ diff --git a/compat/linux/sys/mman.h b/compat/linux/sys/mman.h new file mode 100644 index 00000000..61c55d7b --- /dev/null +++ b/compat/linux/sys/mman.h @@ -0,0 +1,40 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_COMPAT_LINUX_SYS_MMAN_H_ +#define CRASHPAD_COMPAT_LINUX_SYS_MMAN_H_ + +#include_next + +#include + +// There's no memfd_create() wrapper before glibc 2.27. +// This can't select for glibc < 2.27 because linux-chromeos-rel bots build this +// code using a sysroot which has glibc 2.27, but then run it on Ubuntu 16.04, +// which doesn't. +#if defined(__GLIBC__) + +#ifdef __cplusplus +extern "C" { +#endif + +int memfd_create(const char* name, unsigned int flags); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // __GLIBC__ + +#endif // CRASHPAD_COMPAT_LINUX_SYS_MMAN_H_ diff --git a/compat/linux/sys/ptrace.h b/compat/linux/sys/ptrace.h index c5e37a0f..f8be372c 100644 --- a/compat/linux/sys/ptrace.h +++ b/compat/linux/sys/ptrace.h @@ -26,7 +26,8 @@ static constexpr __ptrace_request PTRACE_GET_THREAD_AREA = static_cast<__ptrace_request>(25); #define PTRACE_GET_THREAD_AREA PTRACE_GET_THREAD_AREA -#elif defined(__arm__) || defined(__aarch64__) +// https://bugs.chromium.org/p/chromium/issues/detail?id=873168 +#elif defined(__arm__) || (defined(__aarch64__) && __GLIBC_PREREQ(2,28)) static constexpr __ptrace_request PTRACE_GET_THREAD_AREA = static_cast<__ptrace_request>(22); #define PTRACE_GET_THREAD_AREA PTRACE_GET_THREAD_AREA diff --git a/compat/mac/AvailabilityMacros.h b/compat/mac/AvailabilityMacros.h index f105f944..a83d2a42 100644 --- a/compat/mac/AvailabilityMacros.h +++ b/compat/mac/AvailabilityMacros.h @@ -59,4 +59,16 @@ #define MAC_OS_X_VERSION_10_13 101300 #endif +// 10.14 SDK + +#ifndef MAC_OS_X_VERSION_10_14 +#define MAC_OS_X_VERSION_10_14 101400 +#endif + +// 10.15 SDK + +#ifndef MAC_OS_X_VERSION_10_15 +#define MAC_OS_X_VERSION_10_15 101500 +#endif + #endif // CRASHPAD_COMPAT_MAC_AVAILABILITYMACROS_H_ diff --git a/compat/non_elf/elf.h b/compat/non_elf/elf.h new file mode 100644 index 00000000..1245f16d --- /dev/null +++ b/compat/non_elf/elf.h @@ -0,0 +1,20 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_COMPAT_NON_ELF_ELF_H_ +#define CRASHPAD_COMPAT_NON_ELF_ELF_H_ + +#include "third_party/glibc/elf/elf.h" + +#endif // CRASHPAD_COMPAT_NON_ELF_ELF_H_ diff --git a/compat/non_mac/mach-o/loader.h b/compat/non_mac/mach-o/loader.h new file mode 100644 index 00000000..98a8b5fa --- /dev/null +++ b/compat/non_mac/mach-o/loader.h @@ -0,0 +1,20 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_COMPAT_NON_MAC_MACH_O_LOADER_H_ +#define CRASHPAD_COMPAT_NON_MAC_MACH_O_LOADER_H_ + +#include "third_party/xnu/EXTERNAL_HEADERS/mach-o/loader.h" + +#endif // CRASHPAD_COMPAT_NON_MAC_MACH_O_LOADER_H_ diff --git a/compat/non_mac/mach/machine.h b/compat/non_mac/mach/machine.h new file mode 100644 index 00000000..54d4742b --- /dev/null +++ b/compat/non_mac/mach/machine.h @@ -0,0 +1,21 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_COMPAT_NON_MAC_MACH_MACHINE_H_ +#define CRASHPAD_COMPAT_NON_MAC_MACH_MACHINE_H_ + +typedef int cpu_type_t; +typedef int cpu_subtype_t; + +#endif // CRASHPAD_COMPAT_NON_MAC_MACH_MACHINE_H_ diff --git a/compat/non_mac/mach/vm_prot.h b/compat/non_mac/mach/vm_prot.h new file mode 100644 index 00000000..12001791 --- /dev/null +++ b/compat/non_mac/mach/vm_prot.h @@ -0,0 +1,20 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_COMPAT_NON_MAC_MACH_VM_PROT_H_ +#define CRASHPAD_COMPAT_NON_MAC_MACH_VM_PROT_H_ + +typedef int vm_prot_t; + +#endif // CRASHPAD_COMPAT_NON_MAC_MACH_VM_PROT_H_ diff --git a/compat/non_win/dbghelp.h b/compat/non_win/dbghelp.h index 5ce88b88..ded912e7 100644 --- a/compat/non_win/dbghelp.h +++ b/compat/non_win/dbghelp.h @@ -214,7 +214,7 @@ union __attribute__((packed, aligned(4))) CPU_INFORMATION { //! `cpuid 0x80000001` `edx`. //! //! This field is only valid if #VendorId identifies the CPU vendor as - //! “AuthenticAMD”. + //! “AuthenticAMD” or "HygonGenuine". uint32_t AMDExtendedCpuFeatures; } X86CpuInfo; diff --git a/compat/win/sys/types.h b/compat/win/sys/types.h index fcded87d..e8fae88f 100644 --- a/compat/win/sys/types.h +++ b/compat/win/sys/types.h @@ -20,6 +20,4 @@ #include -typedef unsigned int pid_t; - #endif // CRASHPAD_COMPAT_WIN_SYS_TYPES_H_ diff --git a/doc/developing.md b/doc/developing.md index 84d1cf76..431ca1d5 100644 --- a/doc/developing.md +++ b/doc/developing.md @@ -106,68 +106,67 @@ 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. +#### Fuchsia + +In order to instruct gclient to download the Fuchsia SDK, you need to add the +following to `~/crashpad/.gclient`. + +``` +target_os=["fuchsia"] +``` + +If you're using this tree to develop for multiple targets, you can also add +other entries to the the list (e.g. `target_os=["fuchsia", "mac"]`). + +#### Optional Linux Configs + +To pull and use Crashpad's version of clang and sysroot, make the following +changes. + +Add the following to `~/crashpad/.gclient`. +``` +"custom_vars": { "pull_linux_clang": True }, +``` +Add these args to `out/Default/args.gn`. +``` +clang_path = "//third_party/linux/clang/linux-amd64" +target_sysroot = "//third_party/linux/sysroot" +``` + ### Android -Crashpad’s Android port is in its early stages. This build relies on -cross-compilation. It’s possible to develop Crashpad for Android on any platform -that the [Android NDK (Native Development -Kit)](https://developer.android.com/ndk/) runs on. +This build relies on cross-compilation. It’s possible to develop Crashpad for +Android on any platform that the [Android NDK (Native Development Kit)] +(https://developer.android.com/ndk/) runs on. If it’s not already present on your system, [download the NDK package for your system](https://developer.android.com/ndk/downloads/) and expand it to a suitable location. These instructions assume that it’s been expanded to -`~/android-ndk-r16`. - -To build Crashpad, portions of the NDK must be reassembled into a [standalone -toolchain](https://developer.android.com/ndk/guides/standalone_toolchain.html). -This is a repackaged subset of the NDK suitable for cross-compiling for a single -Android architecture (such as `arm`, `arm64`, `x86`, and `x86_64`) targeting a -specific [Android API -level](https://source.android.com/source/build-numbers.html). The standalone -toolchain only needs to be built from the NDK one time for each set of options -desired. To build a standalone toolchain targeting 64-bit ARM and API level 21 -(Android 5.0 “Lollipop”), run: - -``` -$ cd ~ -$ python android-ndk-r16/build/tools/make_standalone_toolchain.py \ - --arch=arm64 --api=21 --install-dir=android-ndk-r16_arm64_api21 -``` +`~/android-ndk-r20`. Note that Chrome uses Android API level 21 for 64-bit platforms and 16 for 32-bit platforms. See Chrome’s [`build/config/android/config.gni`](https://chromium.googlesource.com/chromium/src/+/master/build/config/android/config.gni) which sets `_android_api_level` and `_android64_api_level`. -To configure a Crashpad build for Android using the standalone toolchain -assembled above, use `gyp_crashpad_android.py`. This script is a wrapper for -`gyp_crashpad.py` that sets several environment variables directing the build to -the standalone toolchain, and several GYP options to identify an Android build. -This must be done after any `gclient sync`, or instead of any `gclient runhooks` -operation. +To configure a Crashpad build for Android use `gyp_crashpad_android.py`. This +script is a wrapper for `gyp_crashpad.py` that sets several environment +variables directing the build to the toolchain, and several GYP options to +identify an Android build. This must be done after any `gclient sync`, or +instead of any `gclient runhooks` operation. ``` $ cd ~/crashpad/crashpad -$ python build/gyp_crashpad_android.py \ - --ndk ~/android-ndk-r16_arm64_api21 \ - --generator-output out/android_arm64_api21 +python build/gyp_crashpad_android.py \ + --ndk ~/usr/lib/android-ndk-r20 --arch arm64 --api-level 21 \ + --generator-output=out/android_arm64_api21 \ ``` -`gyp_crashpad_android.py` detects the build type based on the characteristics of -the standalone toolchain given in its `--ndk` argument. - -`gyp_crashpad_android.py` sets the build up to use Clang by default. It’s also -possible to use GCC by providing the `--compiler=gcc` argument to -`gyp_crashpad_android.py`. - -The Android port is incomplete, but targets known to be working include -`crashpad_test`, `crashpad_util`, and their tests. This list will grow over -time. To build, direct `ninja` to the specific `out` directory chosen by the +To build, direct `ninja` to the specific `out` directory chosen by the `--generator-output` argument to `gyp_crashpad_android.py`. ``` -$ ninja -C out/android_arm64_api21/out/Debug \ - crashpad_test_test crashpad_util_test +$ ninja -C out/android_arm64_api21/out/Debug all ``` ## Testing @@ -193,6 +192,13 @@ $ cd ~/crashpad/crashpad $ python build/run_tests.py out/Debug ``` +To run a subset of the tests, use the --gtest\_filter flag, e.g., to run all the +tests for MinidumpStringWriter: + +```sh +$ python build/run_tests.py out/Debug --gtest_filter MinidumpStringWriter* +``` + ### Windows On Windows, `end_to_end_test.py` requires the CDB debugger, installed with @@ -220,6 +226,25 @@ 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. +### Fuchsia + +To test on Fuchsia, you need a connected device running Fuchsia and then run: + +```sh +$ gn gen out/fuchsia --args='target_os="fuchsia" target_cpu="x64" is_debug=true' +$ ninja -C out/fuchsia +$ python build/run_tests.py out/fuchsia +``` + +If you have multiple devices running, you will need to specify which device you +want using their hostname, for instance: + +```sh +$ export ZIRCON_NODENAME=scare-brook-skip-dried; \ + python build/run_tests.py out/fuchsia; \ + unset ZIRCON_NODENAME +``` + ## Contributing Crashpad’s contribution process is very similar to [Chromium’s contribution @@ -303,7 +328,7 @@ file. ## Buildbot -The [Crashpad Buildbot](https://build.chromium.org/p/client.crashpad/) performs -automated builds and tests of Crashpad. Before checking out or updating the -Crashpad source code, and after checking in a new change, it is prudent to check -the Buildbot to ensure that “the tree is green.” +The [Crashpad Buildbot](https://ci.chromium.org/p/crashpad/g/main/console) +performs automated builds and tests of Crashpad. Before checking out or updating +the Crashpad source code, and after checking in a new change, it is prudent to +check the Buildbot to ensure that “the tree is green.” diff --git a/doc/overview_design.md b/doc/overview_design.md index 07a7d651..9d0f0edd 100644 --- a/doc/overview_design.md +++ b/doc/overview_design.md @@ -240,7 +240,24 @@ of command line arguments in this mode. #### Linux/Android -TODO(mmentovai): describe this. See this preliminary doc. +On Linux, a registration is a connected socket pair between a client process and +the Crashpad handler. This socket pair may be private or shared among many +client processes. + +##### Private Connections + +Private connections are the default registration mode when starting the handler +process in response to a crash or on behalf of another client. This mode is +required to use a ptrace broker, which is in turn required to trace Android +isolated processes. + +##### Shared Connections + +Shared connections are the default mode when using a long-lived handler. The +same connected socket pair may be shared among any number of clients. The socket +pair is created by the first process to start the handler at which point the +client socket end may be shared with other clients by any convenient means (e.g. +inheritance). ### Capturing Exceptions @@ -290,8 +307,16 @@ here.](https://crashpad.chromium.org/bug/133) #### Linux/Android -TODO(mmentovai): describe this. See [this preliminary -doc.](https://goto.google.com/crashpad-android-dd) +On Linux, exceptions are dispatched as signals to the crashing thread. Crashpad +signal handlers will send a message over the socket to the Crashpad handler +notifying it of the crash and the location of exception information to be read +from the crashing process. When using a shared socket connection, communication +is entirely one-way. The client sends its dump request to the handler and then +waits until the handler responds with a SIGCONT or a timeout occurs. When using +a private socket connection, the handler may respond over the socket to +communicate with a ptrace broker process. The broker is forked from the crashing +process, executes ptrace requests against the crashing process, and sends the +information over the socket to the handler. ### The CrashpadInfo structure diff --git a/doc/status.md b/doc/status.md index dcb6e62e..3948b449 100644 --- a/doc/status.md +++ b/doc/status.md @@ -18,24 +18,27 @@ limitations under the License. ## Completed -Crashpad currently consists of a crash-reporting client and some related tools -for macOS and Windows. The core client work for both platforms is substantially -complete. Crashpad became the crash reporter client for -[Chromium](https://www.chromium.org/Home) on macOS as of [March +Crashpad has complete crash-reporting clients and some related tools for macOS, +Windows, Fuchsia, and Linux (including Android and Chromium OS). Crashpad became +the crash reporter client for [Chromium](https://www.chromium.org/Home) on macOS +as of [March 2015](https://chromium.googlesource.com/chromium/src/\+/d413b2dcb54d523811d386f1ff4084f677a6d089), -and on Windows as of [November -2015](https://chromium.googlesource.com/chromium/src/\+/cfa5b01bb1d06bf96967bd37e21a44752801948c). +Windows as of [November +2015](https://chromium.googlesource.com/chromium/src/\+/cfa5b01bb1d06bf96967bd37e21a44752801948c), +and Android as of [January +2019](https://chromium.googlesource.com/chromium/src/+/f890e4b5495ab693d2d37aec3c378239946154f7). + ## In Progress -Initial work on a Crashpad client for -[Android](https://crashpad.chromium.org/bug/30) has begun. This is currently in -the early implementation phase. +Chromium is transitioning to Crashpad for [Chromium OS and Desktop +Linux](https://crbug.com/942279). + +Work has begun on a Crashpad client for +[iOS](https://crashpad.chromium.org/bug/31). ## Future -There are plans to bring Crashpad clients to other operating systems in the -future, including a more generic non-Android Linux implementation. There are -also plans to implement a [crash report +There are also plans to implement a [crash report processor](https://crashpad.chromium.org/bug/29) as part of Crashpad. No timeline for completing this work has been set yet. diff --git a/doc/support/crashpad.doxy b/doc/support/crashpad.doxy index 2f3e2bb2..ca1ea251 100644 --- a/doc/support/crashpad.doxy +++ b/doc/support/crashpad.doxy @@ -1,4 +1,4 @@ -# Doxyfile 1.8.14 +# Doxyfile 1.8.18 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. @@ -17,10 +17,10 @@ # Project related configuration options #--------------------------------------------------------------------------- -# This tag specifies the encoding used for all characters in the config file -# that follow. The default is UTF-8 which is also the encoding used for all text -# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv -# built into libc) for the transcoding. See +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See # https://www.gnu.org/software/libiconv/ for the list of possible encodings. # The default value is: UTF-8. @@ -93,6 +93,14 @@ ALLOW_UNICODE_NAMES = NO OUTPUT_LANGUAGE = English +# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all generated output in the proper direction. +# Possible values are: None, LTR, RTL and Context. +# The default value is: None. + +OUTPUT_TEXT_DIRECTION = None + # If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member # descriptions after the members that are listed in the file and class # documentation (similar to Javadoc). Set to NO to disable this. @@ -181,6 +189,16 @@ SHORT_NAMES = NO JAVADOC_AUTOBRIEF = NO +# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by doxygen. +# The default value is: NO. + +JAVADOC_BANNER = NO + # If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first # line (until the first dot) of a Qt-style comment as the brief description. If # set to NO, the Qt-style will behave just like regular Qt-style comments (thus @@ -230,15 +248,13 @@ TAB_SIZE = 4 # "Side Effects:". You can put \n's in the value part of an alias to insert # newlines (in the resulting output). You can put ^^ in the value part of an # alias to insert a newline as if a physical newline was in the original file. +# When you need a literal { or } or , in the value part of an alias you have to +# escape them by means of a backslash (\), this can lead to conflicts with the +# commands \{ and \} for these it is advised to use the version @{ and @} or use +# a double escape (\\{ and \\}) ALIASES = -# This tag can be used to specify a number of word-keyword mappings (TCL only). -# A mapping has the form "name=value". For example adding "class=itcl::class" -# will allow you to use the command class in the itcl::class meaning. - -TCL_SUBST = - # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For # instance, some of the names that are used will be different. The list of all @@ -267,17 +283,26 @@ OPTIMIZE_FOR_FORTRAN = NO OPTIMIZE_OUTPUT_VHDL = NO +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = NO + # Doxygen selects the parser to use depending on the extension of the files it # parses. With this tag you can assign which parser to use for a given # extension. Doxygen has a built-in mapping, but you can override or extend it # using this tag. The format is ext=language, where ext is a file extension, and -# language is one of the parsers supported by doxygen: IDL, Java, Javascript, -# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: -# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: -# Fortran. In the later case the parser tries to guess whether the code is fixed -# or free formatted code, this is the default for Fortran type files), VHDL. For -# instance to make doxygen treat .inc files as Fortran files (default is PHP), -# and .f files as C (default is Fortran), use: inc=Fortran f=C. +# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL, +# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files). For instance to make doxygen treat .inc files +# as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. # # Note: For files without extension you can use no_extension as a placeholder. # @@ -288,7 +313,7 @@ EXTENSION_MAPPING = # If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments # according to the Markdown format, which allows for more readable -# documentation. See http://daringfireball.net/projects/markdown/ for details. +# documentation. See https://daringfireball.net/projects/markdown/ for details. # The output of markdown processing is further processed by doxygen, so you can # mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in # case of backward compatibilities issues. @@ -300,7 +325,7 @@ MARKDOWN_SUPPORT = YES # to that level are automatically included in the table of contents, even if # they do not have an id attribute. # Note: This feature currently applies only to Markdown headings. -# Minimum value: 0, maximum value: 99, default value: 0. +# Minimum value: 0, maximum value: 99, default value: 5. # This tag requires that the tag MARKDOWN_SUPPORT is set to YES. TOC_INCLUDE_HEADINGS = 0 @@ -436,6 +461,12 @@ EXTRACT_ALL = NO EXTRACT_PRIVATE = NO +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = NO + # If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal # scope will be included in the documentation. # The default value is: NO. @@ -490,8 +521,8 @@ HIDE_UNDOC_MEMBERS = NO HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend -# (class|struct|union) declarations. If set to NO, these declarations will be -# included in the documentation. +# declarations. If set to NO, these declarations will be included in the +# documentation. # The default value is: NO. HIDE_FRIEND_COMPOUNDS = NO @@ -514,7 +545,7 @@ INTERNAL_DOCS = NO # names in lower-case letters. If set to YES, upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows -# and Mac users are advised to set this option to NO. +# (including Cygwin) ands Mac users are advised to set this option to NO. # The default value is: system dependent. CASE_SENSE_NAMES = YES @@ -746,7 +777,8 @@ WARN_IF_DOC_ERROR = YES # This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that # are documented, but have no documentation for their parameters or return # value. If set to NO, doxygen will only warn about wrong or incomplete -# parameter documentation, but not about the absence of documentation. +# parameter documentation, but not about the absence of documentation. If +# EXTRACT_ALL is set to YES then this flag will automatically be disabled. # The default value is: NO. WARN_NO_PARAMDOC = NO @@ -805,8 +837,10 @@ INPUT_ENCODING = UTF-8 # If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, # *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, # *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, -# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, -# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf. +# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment), +# *.doc (to be provided as doxygen C comment), *.txt (to be provided as doxygen +# C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, +# *.vhdl, *.ucf, *.qsf and *.ice. FILE_PATTERNS = *.c \ *.cc \ @@ -968,7 +1002,7 @@ INLINE_SOURCES = NO STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES then for each documented -# function all documented functions referencing it will be listed. +# entity all documented functions referencing it will be listed. # The default value is: NO. REFERENCED_BY_RELATION = NO @@ -1005,7 +1039,7 @@ SOURCE_TOOLTIPS = YES # # To use it do the following: # - Install the latest version of global -# - Enable SOURCE_BROWSER and USE_HTAGS in the config file +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file # - Make sure the INPUT points to the root of the source tree # - Run doxygen as normal # @@ -1183,9 +1217,9 @@ HTML_TIMESTAMP = NO # If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML # documentation will contain a main index with vertical navigation menus that -# are dynamically created via Javascript. If disabled, the navigation index will +# are dynamically created via JavaScript. If disabled, the navigation index will # consists of multiple levels of tabs that are statically embedded in every HTML -# page. Disable this option to support browsers that do not have Javascript, +# page. Disable this option to support browsers that do not have JavaScript, # like the Qt help browser. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. @@ -1215,13 +1249,13 @@ HTML_INDEX_NUM_ENTRIES = 100 # If the GENERATE_DOCSET tag is set to YES, additional index files will be # generated that can be used as input for Apple's Xcode 3 integrated development -# environment (see: https://developer.apple.com/tools/xcode/), introduced with -# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a +# environment (see: https://developer.apple.com/xcode/), introduced with OSX +# 10.5 (Leopard). To create a documentation set, doxygen will generate a # Makefile in the HTML output directory. Running make will produce the docset in # that directory and running make install will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at -# startup. See https://developer.apple.com/tools/creatingdocsetswithdoxygen.html -# for more information. +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. @@ -1260,7 +1294,7 @@ DOCSET_PUBLISHER_NAME = Publisher # If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three # additional HTML index files: index.hhp, index.hhc, and index.hhk. The # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop -# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on +# (see: https://www.microsoft.com/en-us/download/details.aspx?id=21138) on # Windows. # # The HTML Help Workshop contains a compiler that can convert all HTML output @@ -1336,7 +1370,7 @@ QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help # Project output. For more information please see Qt Help Project / Namespace -# (see: http://doc.qt.io/qt-4.8/qthelpproject.html#namespace). +# (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_QHP is set to YES. @@ -1344,7 +1378,8 @@ QHP_NAMESPACE = org.doxygen.Project # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt # Help Project output. For more information please see Qt Help Project / Virtual -# Folders (see: http://doc.qt.io/qt-4.8/qthelpproject.html#virtual-folders). +# Folders (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual- +# folders). # The default value is: doc. # This tag requires that the tag GENERATE_QHP is set to YES. @@ -1352,21 +1387,23 @@ QHP_VIRTUAL_FOLDER = doc # If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom # filter to add. For more information please see Qt Help Project / Custom -# Filters (see: http://doc.qt.io/qt-4.8/qthelpproject.html#custom-filters). +# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom- +# filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_NAME = # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see Qt Help Project / Custom -# Filters (see: http://doc.qt.io/qt-4.8/qthelpproject.html#custom-filters). +# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom- +# filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # project's filter section matches. Qt Help Project / Filter Attributes (see: -# http://doc.qt.io/qt-4.8/qthelpproject.html#filter-attributes). +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_SECT_FILTER_ATTRS = @@ -1450,6 +1487,17 @@ TREEVIEW_WIDTH = 250 EXT_LINKS_IN_WINDOW = NO +# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png The default and svg Looks nicer but requires the +# pdf2svg tool. +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FORMULA_FORMAT = png + # Use this tag to change the font size of LaTeX formulas included as images in # the HTML documentation. When you change the font size after a successful # doxygen run you need to manually remove any form_*.png images from the HTML @@ -1470,8 +1518,14 @@ FORMULA_FONTSIZE = 10 FORMULA_TRANSPARENT = YES +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. + +FORMULA_MACROFILE = + # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see -# https://www.mathjax.org) which uses client side Javascript for the rendering +# https://www.mathjax.org) which uses client side JavaScript for the rendering # instead of using pre-rendered bitmaps. Use this if you do not have LaTeX # installed or if you want to formulas look prettier in the HTML output. When # enabled you may also need to install MathJax separately and configure the path @@ -1499,7 +1553,7 @@ MATHJAX_FORMAT = HTML-CSS # Content Delivery Network so you can quickly see the result without installing # MathJax. However, it is strongly recommended to install a local copy of # MathJax from https://www.mathjax.org before deployment. -# The default value is: https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/. +# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest @@ -1541,7 +1595,7 @@ MATHJAX_CODEFILE = SEARCHENGINE = YES # When the SERVER_BASED_SEARCH tag is enabled the search engine will be -# implemented using a web server instead of a web client using Javascript. There +# implemented using a web server instead of a web client using JavaScript. There # are two flavors of web server based searching depending on the EXTERNAL_SEARCH # setting. When disabled, doxygen will generate a PHP script for searching and # an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing @@ -1625,21 +1679,35 @@ LATEX_OUTPUT = latex # The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be # invoked. # -# Note that when enabling USE_PDFLATEX this option is only used for generating -# bitmaps for formulas in the HTML output, but not in the Makefile that is -# written to the output directory. -# The default file is: latex. +# Note that when not enabling USE_PDFLATEX the default is latex when enabling +# USE_PDFLATEX the default is pdflatex and when in the later case latex is +# chosen this is overwritten by pdflatex. For specific output languages the +# default can have been set differently, this depends on the implementation of +# the output language. # This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_CMD_NAME = latex # The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate # index for LaTeX. +# Note: This tag is used in the Makefile / make.bat. +# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file +# (.tex). # The default file is: makeindex. # This tag requires that the tag GENERATE_LATEX is set to YES. MAKEINDEX_CMD_NAME = makeindex +# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to +# generate index for LaTeX. In case there is no backslash (\) as first character +# it will be automatically added in the LaTeX code. +# Note: This tag is used in the generated output file (.tex). +# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat. +# The default value is: makeindex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_MAKEINDEX_CMD = makeindex + # If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX # documents. This may be useful for small projects and may help to save some # trees in general. @@ -1774,6 +1842,14 @@ LATEX_BIB_STYLE = plain LATEX_TIMESTAMP = NO +# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute) +# path from which the emoji images will be read. If a relative path is entered, +# it will be relative to the LATEX_OUTPUT directory. If left blank the +# LATEX_OUTPUT directory will be used. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EMOJI_DIRECTORY = + #--------------------------------------------------------------------------- # Configuration options related to the RTF output #--------------------------------------------------------------------------- @@ -1813,9 +1889,9 @@ COMPACT_RTF = NO RTF_HYPERLINKS = NO -# Load stylesheet definitions from file. Syntax is similar to doxygen's config -# file, i.e. a series of assignments. You only have to provide replacements, -# missing definitions are set to their default value. +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# configuration file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. # # See also section "Doxygen usage" for information on how to generate the # default style sheet that doxygen normally uses. @@ -1824,8 +1900,8 @@ RTF_HYPERLINKS = NO RTF_STYLESHEET_FILE = # Set optional variables used in the generation of an RTF document. Syntax is -# similar to doxygen's config file. A template extensions file can be generated -# using doxygen -e rtf extensionFile. +# similar to doxygen's configuration file. A template extensions file can be +# generated using doxygen -e rtf extensionFile. # This tag requires that the tag GENERATE_RTF is set to YES. RTF_EXTENSIONS_FILE = @@ -1911,6 +1987,13 @@ XML_OUTPUT = xml XML_PROGRAMLISTING = YES +# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include +# namespace members in file scope as well, matching the HTML output. +# The default value is: NO. +# This tag requires that the tag GENERATE_XML is set to YES. + +XML_NS_MEMB_FILE_SCOPE = NO + #--------------------------------------------------------------------------- # Configuration options related to the DOCBOOK output #--------------------------------------------------------------------------- @@ -2116,12 +2199,6 @@ EXTERNAL_GROUPS = YES EXTERNAL_PAGES = YES -# The PERL_PATH should be the absolute path and name of the perl script -# interpreter (i.e. the result of 'which perl'). -# The default file (with absolute path) is: /usr/bin/perl. - -PERL_PATH = /usr/bin/perl - #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- @@ -2135,15 +2212,6 @@ PERL_PATH = /usr/bin/perl CLASS_DIAGRAMS = YES -# You can define message sequence charts within doxygen comments using the \msc -# command. Doxygen will then run the mscgen tool (see: -# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the -# documentation. The MSCGEN_PATH tag allows you to specify the directory where -# the mscgen tool resides. If left empty the tool is assumed to be found in the -# default search path. - -MSCGEN_PATH = - # You can include diagrams made with dia in doxygen documentation. Doxygen will # then run dia to produce the diagram and insert it in the documentation. The # DIA_PATH tag allows you to specify the directory where the dia binary resides. diff --git a/doc/support/generate_doxygen.py b/doc/support/generate_doxygen.py index a93028df..577b51e5 100755 --- a/doc/support/generate_doxygen.py +++ b/doc/support/generate_doxygen.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding: utf-8 # Copyright 2017 The Crashpad Authors. All rights reserved. # @@ -24,26 +23,26 @@ import sys def main(args): - script_dir = os.path.dirname(__file__) - crashpad_dir = os.path.join(script_dir, os.pardir, os.pardir) + script_dir = os.path.dirname(__file__) + crashpad_dir = os.path.join(script_dir, os.pardir, os.pardir) - # Run from the Crashpad project root directory. - os.chdir(crashpad_dir) + # Run from the Crashpad project root directory. + os.chdir(crashpad_dir) - output_dir = os.path.join('out', 'doc', 'doxygen') + output_dir = os.path.join('out', 'doc', 'doxygen') - if os.path.isdir(output_dir) and not os.path.islink(output_dir): - shutil.rmtree(output_dir) - elif os.path.exists(output_dir): - os.unlink(output_dir) + if os.path.isdir(output_dir) and not os.path.islink(output_dir): + shutil.rmtree(output_dir) + elif os.path.exists(output_dir): + os.unlink(output_dir) - os.makedirs(output_dir, 0o755) + os.makedirs(output_dir, 0o755) - doxy_file = os.path.join('doc', 'support', 'crashpad.doxy') - subprocess.check_call(['doxygen', doxy_file]) + doxy_file = os.path.join('doc', 'support', 'crashpad.doxy') + subprocess.check_call(['doxygen', doxy_file]) - return 0 + return 0 if __name__ == '__main__': - sys.exit(main(sys.argv[1:])) + sys.exit(main(sys.argv[1:])) diff --git a/handler/BUILD.gn b/handler/BUILD.gn index 2eae30b5..17c4cbd6 100644 --- a/handler/BUILD.gn +++ b/handler/BUILD.gn @@ -42,6 +42,8 @@ static_library("handler") { if (crashpad_is_linux || crashpad_is_android) { set_sources_assignment_filter([]) sources += [ + "linux/capture_snapshot.cc", + "linux/capture_snapshot.h", "linux/crash_report_exception_handler.cc", "linux/crash_report_exception_handler.h", "linux/exception_handler_server.cc", @@ -49,6 +51,13 @@ static_library("handler") { ] } + if (crashpad_is_linux) { + sources += [ + "linux/cros_crash_report_exception_handler.cc", + "linux/cros_crash_report_exception_handler.h", + ] + } + if (crashpad_is_win) { sources += [ "win/crash_report_exception_handler.cc", @@ -56,25 +65,18 @@ static_library("handler") { ] } - if (crashpad_is_fuchsia) { - sources += [ - "fuchsia/crash_report_exception_handler.cc", - "fuchsia/crash_report_exception_handler.h", - "fuchsia/exception_handler_server.cc", - "fuchsia/exception_handler_server.h", - ] - } - public_configs = [ "..:crashpad_config" ] - deps = [ + public_deps = [ "../client", - "../compat", + "../third_party/mini_chromium:base", + "../util", + ] + + deps = [ "../minidump", "../snapshot", - "../third_party/mini_chromium:base", "../tools:tool_support", - "../util", ] if (crashpad_is_win) { @@ -82,12 +84,20 @@ static_library("handler") { } } +if (crashpad_is_android) { + # CrashpadHandlerMain is defined in a separate target so that it can be + # overriden by implementers + source_set("crashpad_handler_main") { + sources = [ "crashpad_handler_main.cc" ] + + deps = [ ":handler" ] + } +} + source_set("handler_test") { testonly = true - sources = [ - "minidump_to_upload_parameters_test.cc", - ] + sources = [ "minidump_to_upload_parameters_test.cc" ] if (crashpad_is_linux || crashpad_is_android) { sources += [ "linux/exception_handler_server_test.cc" ] @@ -110,6 +120,8 @@ source_set("handler_test") { ] if (crashpad_is_win) { + deps += [ "../minidump:test_support" ] + data_deps = [ ":crashpad_handler_test_extended_handler", ":fake_handler_that_crashes_at_startup", @@ -117,45 +129,28 @@ source_set("handler_test") { } } -crashpad_executable("crashpad_handler") { - sources = [ - "main.cc", - ] +if (!crashpad_is_ios) { + crashpad_executable("crashpad_handler") { + sources = [ "main.cc" ] - deps = [ - ":handler", - "../build:default_exe_manifest_win", - "../compat", - "../third_party/mini_chromium:base", - ] + deps = [ + ":handler", + "../build:default_exe_manifest_win", + "../compat", + "../third_party/mini_chromium:base", + "../tools:tool_support", + ] - if (crashpad_is_mac && crashpad_is_in_chromium) { - if (is_component_build) { - ldflags = [ - # The handler is in - # Chromium.app/Contents/Versions/X/Chromium Framework.framework/Versions/A/Helpers/ - # so set rpath up to the base. - "-rpath", - "@loader_path/../../../../../../../..", - - # The handler is also in - # Content Shell.app/Contents/Frameworks/Content Shell Framework.framework/Helpers/ - # so set the rpath for that too. - "-rpath", - "@loader_path/../../../../..", - ] - } - } - - if (crashpad_is_win) { - if (crashpad_is_in_chromium) { - remove_configs = [ "//build/config/win:console" ] - configs = [ "//build/config/win:windowed" ] - } else { - remove_configs = - [ "//third_party/mini_chromium/mini_chromium/build:win_console" ] - configs = - [ "//third_party/mini_chromium/mini_chromium/build:win_windowed" ] + if (crashpad_is_win) { + if (crashpad_is_in_chromium || crashpad_is_in_dart) { + remove_configs = [ "//build/config/win:console" ] + configs = [ "//build/config/win:windowed" ] + } else { + remove_configs = + [ "//third_party/mini_chromium/mini_chromium/build:win_console" ] + configs = + [ "//third_party/mini_chromium/mini_chromium/build:win_windowed" ] + } } } } @@ -166,42 +161,47 @@ crashpad_executable("crashpad_handler") { # handler executable an acceptable name. if (crashpad_is_android) { copy("crashpad_handler_named_as_so") { + deps = [ ":crashpad_handler" ] + + sources = [ "$root_out_dir/crashpad_handler" ] + + outputs = [ "$root_out_dir/libcrashpad_handler.so" ] + } + + crashpad_executable("crashpad_handler_trampoline") { + set_sources_assignment_filter([]) + output_name = "libcrashpad_handler_trampoline.so" + + sources = [ "linux/handler_trampoline.cc" ] + + ldflags = [ "-llog" ] + + if (crashpad_is_in_chromium) { + no_default_deps = true + } + } +} + +if (!crashpad_is_ios) { + crashpad_executable("crashpad_handler_test_extended_handler") { + testonly = true + + sources = [ "crashpad_handler_test_extended_handler.cc" ] + deps = [ - ":crashpad_handler", - ] - - sources = [ - "$root_out_dir/crashpad_handler", - ] - - outputs = [ - "$root_out_dir/libcrashpad_handler.so", + ":handler", + "../build:default_exe_manifest_win", + "../compat", + "../minidump:test_support", + "../third_party/mini_chromium:base", + "../tools:tool_support", ] } } -crashpad_executable("crashpad_handler_test_extended_handler") { - testonly = true - - sources = [ - "crashpad_handler_test_extended_handler.cc", - ] - - deps = [ - ":handler", - "../build:default_exe_manifest_win", - "../compat", - "../minidump:test_support", - "../third_party/mini_chromium:base", - "../tools:tool_support", - ] -} - if (crashpad_is_win) { crashpad_executable("crashpad_handler_com") { - sources = [ - "main.cc", - ] + sources = [ "main.cc" ] # Avoid .exp, .ilk, and .lib file collisions with crashpad_handler.exe by # having this target produce crashpad_handler_com.com. Don’t use this target @@ -213,27 +213,20 @@ if (crashpad_is_win) { "../build:default_exe_manifest_win", "../compat", "../third_party/mini_chromium:base", + "../tools:tool_support", ] } copy("crashpad_handler_console") { - deps = [ - ":crashpad_handler_com", - ] - sources = [ - "$root_out_dir/crashpad_handler_com.com", - ] - outputs = [ - "$root_out_dir/crashpad_handler.com", - ] + deps = [ ":crashpad_handler_com" ] + sources = [ "$root_out_dir/crashpad_handler_com.com" ] + outputs = [ "$root_out_dir/crashpad_handler.com" ] } crashpad_executable("crash_other_program") { testonly = true - sources = [ - "win/crash_other_program.cc", - ] + sources = [ "win/crash_other_program.cc" ] deps = [ "../client", @@ -246,9 +239,7 @@ if (crashpad_is_win) { crashpad_executable("crashy_program") { testonly = true - sources = [ - "win/crashy_test_program.cc", - ] + sources = [ "win/crashy_test_program.cc" ] deps = [ "../client", @@ -259,9 +250,7 @@ if (crashpad_is_win) { crashpad_executable("crashy_signal") { testonly = true - sources = [ - "win/crashy_signal.cc", - ] + sources = [ "win/crashy_signal.cc" ] cflags = [ "/wd4702" ] # Unreachable code. @@ -274,17 +263,13 @@ if (crashpad_is_win) { crashpad_executable("fake_handler_that_crashes_at_startup") { testonly = true - sources = [ - "win/fake_handler_that_crashes_at_startup.cc", - ] + sources = [ "win/fake_handler_that_crashes_at_startup.cc" ] } crashpad_executable("hanging_program") { testonly = true - sources = [ - "win/hanging_program.cc", - ] + sources = [ "win/hanging_program.cc" ] deps = [ "../client", @@ -295,17 +280,13 @@ if (crashpad_is_win) { crashpad_loadable_module("loader_lock_dll") { testonly = true - sources = [ - "win/loader_lock_dll.cc", - ] + sources = [ "win/loader_lock_dll.cc" ] } crashpad_executable("self_destroying_program") { testonly = true - sources = [ - "win/self_destroying_test_program.cc", - ] + sources = [ "win/self_destroying_test_program.cc" ] deps = [ "../client", @@ -320,9 +301,7 @@ if (crashpad_is_win) { crashpad_executable("crashy_z7_loader") { testonly = true - sources = [ - "win/crashy_test_z7_loader.cc", - ] + sources = [ "win/crashy_test_z7_loader.cc" ] deps = [ "../client", diff --git a/handler/crash_report_upload_thread.cc b/handler/crash_report_upload_thread.cc index 205d860f..e144bddc 100644 --- a/handler/crash_report_upload_thread.cc +++ b/handler/crash_report_upload_thread.cc @@ -246,12 +246,6 @@ void CrashReportUploadThread::ProcessPendingReport( CrashReportUploadThread::UploadResult CrashReportUploadThread::UploadReport( const CrashReportDatabase::UploadReport* report, std::string* response_body) { -#if defined(OS_ANDROID) - // TODO(jperaza): This method can be enabled on Android after HTTPTransport is - // implemented and Crashpad takes over upload responsibilty on Android. - NOTREACHED(); - return UploadResult::kPermanentFailure; -#else std::map parameters; FileReader* reader = report->Reader(); @@ -338,7 +332,6 @@ CrashReportUploadThread::UploadResult CrashReportUploadThread::UploadReport( } return UploadResult::kSuccess; -#endif // OS_ANDROID } void CrashReportUploadThread::DoWork(const WorkerThread* thread) { diff --git a/handler/crashpad_handler.md b/handler/crashpad_handler.md index 003ee2ef..6d055ed1 100644 --- a/handler/crashpad_handler.md +++ b/handler/crashpad_handler.md @@ -121,13 +121,6 @@ establish the Crashpad client environment before running a program. Either this option or **--mach-service**, but not both, is required. This option is only valid on macOS. - * **--no-identify-client-via-url** - - Do not add client-identifying fields to the URL. By default, `"prod"`, - `"ver"`, and `"guid"` annotations are added to the upload URL as name-value - pairs `"product"`, `"version"`, and `"guid"`, respectively. Using this - option disables that behavior. - * **--initial-client-data**=*HANDLE_request_crash_dump*,*HANDLE_request_non_crash_dump*,*HANDLE_non_crash_dump_completed*,*HANDLE_first_pipe_instance*,*HANDLE_client_process*,*Address_crash_exception_information*,*Address_non_crash_exception_information*,*Address_debug_critical_section* Register the initial client using the inherited handles and data provided. @@ -141,6 +134,13 @@ establish the Crashpad client environment before running a program. client to register, and exits when all clients have exited, after waiting for any uploads in progress to complete. + * **--initial-client-fd**=_FD_ + + Wait for client requests on _FD_. Either this option or + **--trace-parent-with-exception**, but not both, is required. The handler + exits when all client connections have been closed. This option is only valid + on Linux platforms. + * **--mach-service**=_SERVICE_ Check in with the bootstrap server under the name _SERVICE_. Either this @@ -198,6 +198,13 @@ establish the Crashpad client environment before running a program. To prevent excessive accumulation of handler processes, _ARGUMENT_ must not be `--monitor-self`. +* **--no-identify-client-via-url** + + Do not add client-identifying fields to the URL. By default, `"prod"`, + `"ver"`, and `"guid"` annotations are added to the upload URL as name-value + pairs `"product"`, `"version"`, and `"guid"`, respectively. Using this + option disables that behavior. + * **--no-periodic-tasks** Do not scan for new pending crash reports or prune the crash report database. @@ -226,6 +233,12 @@ establish the Crashpad client environment before running a program. for use with collection servers that don’t accept uploads compressed in this way. + * **--no-write-minidump-to-database** + + Do not write the minidump to database. Normally, the minidump is written to + database for upload. Use this option with **--write-minidump-to-log** to + only write the minidump to log. This option is only available to Android. + * **--pipe-name**=_PIPE_ Listen on the given pipe name for connections from clients. _PIPE_ must be of @@ -245,17 +258,24 @@ 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. + * **--sanitization-information**=_SANITIZATION-INFORMATION-ADDRESS_ + + Provides sanitization settings in a SanitizationInformation struct at + _SANITIZATION-INFORMATION-ADDRESS_. This option requires + **--trace-parent-with-exception** and is only valid on Linux platforms. + + * **--shared-client-connection** + + Indicates that the file descriptor provided by **--initial-client-fd** is + shared among mulitple clients. Using a broker process is not supported for + clients using this option. This option is only valid on Linux platforms. + * **--trace-parent-with-exception**=_EXCEPTION-INFORMATION-ADDRESS_ Causes the handler process to trace its parent process and exit. The parent process should have an ExceptionInformation struct at - _EXCEPTION-INFORMATION-ADDRESS_. - - * **--initial-client-fd**=_FD_ - - Starts the excetion handler server with an initial ExceptionHandlerClient - connected on the socket _FD_. The server will exit when all connected client - sockets have been closed. + _EXCEPTION-INFORMATION-ADDRESS_. This option is only valid on Linux + platforms. * **--url**=_URL_ @@ -265,6 +285,19 @@ establish the Crashpad client environment before running a program. library, typically in response to a user requesting this behavior. If this option is not specified, this program will behave as if uploads are disabled. + * **--use-cros-crash-reporter** + + Causes crash reports to be passed via an in-memory file to + `/sbin/crash_reporter` instead of storing them in the database. The database + is still used for Crashpad settings. This option is only valid on Chromium + OS. + +* **--write-minidump-to-log** + + Write the minidump to log. By default the minidump is only written to + database. Use this option with **--no-write-minidump-to-database** to only + write the minidump to log. This option is only available to Android. + * **--help** Display help and exit. diff --git a/util/net/http_transport_none.cc b/handler/crashpad_handler_main.cc similarity index 67% rename from util/net/http_transport_none.cc rename to handler/crashpad_handler_main.cc index 1c08c1f1..b47488ef 100644 --- a/util/net/http_transport_none.cc +++ b/handler/crashpad_handler_main.cc @@ -1,4 +1,4 @@ -// Copyright 2017 The Crashpad Authors. All rights reserved. +// Copyright 2019 The Crashpad Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,15 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "util/net/http_transport.h" - -#include "base/logging.h" +#include "handler/handler_main.h" namespace crashpad { -std::unique_ptr HTTPTransport::Create() { - NOTREACHED(); // TODO(scottmg): https://crashpad.chromium.org/bug/196 - return std::unique_ptr(); +extern "C" { + +__attribute__((visibility("default"), used)) int CrashpadHandlerMain( + int argc, + char* argv[]) { + return HandlerMain(argc, argv, nullptr); } +} // extern "C" + } // namespace crashpad diff --git a/handler/fuchsia/crash_report_exception_handler.cc b/handler/fuchsia/crash_report_exception_handler.cc deleted file mode 100644 index 1da09851..00000000 --- a/handler/fuchsia/crash_report_exception_handler.cc +++ /dev/null @@ -1,182 +0,0 @@ -// Copyright 2017 The Crashpad Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "handler/fuchsia/crash_report_exception_handler.h" - -#include - -#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 deleted file mode 100644 index 5043d7e9..00000000 --- a/handler/fuchsia/crash_report_exception_handler.h +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright 2017 The Crashpad Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef CRASHPAD_HANDLER_FUCHSIA_CRASH_REPORT_EXCEPTION_HANDLER_H_ -#define CRASHPAD_HANDLER_FUCHSIA_CRASH_REPORT_EXCEPTION_HANDLER_H_ - -#include -#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 deleted file mode 100644 index 63153d28..00000000 --- a/handler/fuchsia/exception_handler_server.cc +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2017 The Crashpad Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "handler/fuchsia/exception_handler_server.h" - -#include -#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 deleted file mode 100644 index 184d1489..00000000 --- a/handler/fuchsia/exception_handler_server.h +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2017 The Crashpad Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef CRASHPAD_HANDLER_FUCHSIA_EXCEPTION_HANDLER_SERVER_H_ -#define CRASHPAD_HANDLER_FUCHSIA_EXCEPTION_HANDLER_SERVER_H_ - -#include "base/macros.h" -#include "base/fuchsia/scoped_zx_handle.h" - -namespace crashpad { - -class CrashReportExceptionHandler; - -//! \brief Runs the main exception-handling server in Crashpad's handler -//! process. -class ExceptionHandlerServer { - public: - //! \brief Constructs an ExceptionHandlerServer object. - //! - //! \param[in] root_job The root of the tree of processes that will be handled - //! by this server. It is assumed that \a exception_port is the exception - //! port of this job. - //! \param[in] exception_port The exception port that this server will - //! monitor. - ExceptionHandlerServer(base::ScopedZxHandle root_job, - base::ScopedZxHandle exception_port); - ~ExceptionHandlerServer(); - - //! \brief Runs the exception-handling server. - //! - //! \param[in] handler The handler to which the exceptions are delegated when - //! they are caught in Run(). Ownership is not transferred. - void Run(CrashReportExceptionHandler* handler); - - private: - base::ScopedZxHandle root_job_; - base::ScopedZxHandle exception_port_; - - DISALLOW_COPY_AND_ASSIGN(ExceptionHandlerServer); -}; - -} // namespace crashpad - -#endif // CRASHPAD_HANDLER_FUCHSIA_EXCEPTION_HANDLER_SERVER_H_ diff --git a/handler/handler.gyp b/handler/handler.gyp index 60a6f251..f8313a53 100644 --- a/handler/handler.gyp +++ b/handler/handler.gyp @@ -28,6 +28,7 @@ '../minidump/minidump.gyp:crashpad_minidump', '../snapshot/snapshot.gyp:crashpad_snapshot', '../third_party/mini_chromium/mini_chromium.gyp:base', + '../third_party/zlib/zlib.gyp:zlib', '../tools/tools.gyp:crashpad_tool_support', '../util/util.gyp:crashpad_util', ], @@ -39,6 +40,8 @@ 'crash_report_upload_thread.h', 'handler_main.cc', 'handler_main.h', + 'linux/capture_snapshot.cc', + 'linux/capture_snapshot.h', 'linux/crash_report_exception_handler.cc', 'linux/crash_report_exception_handler.h', 'linux/exception_handler_server.cc', diff --git a/handler/handler_main.cc b/handler/handler_main.cc index 412ee112..d56857ff 100644 --- a/handler/handler_main.cc +++ b/handler/handler_main.cc @@ -56,6 +56,10 @@ #include "util/string/split_string.h" #include "util/synchronization/semaphore.h" +#if defined(OS_CHROMEOS) +#include "handler/linux/cros_crash_report_exception_handler.h" +#endif + #if defined(OS_LINUX) || defined(OS_ANDROID) #include @@ -70,8 +74,8 @@ #include "handler/mac/crash_report_exception_handler.h" #include "handler/mac/exception_handler_server.h" #include "handler/mac/file_limit_annotation.h" +#include "util/mach/bootstrap.h" #include "util/mach/child_port_handshake.h" -#include "util/mach/mach_extensions.h" #include "util/posix/close_stdio.h" #include "util/posix/signals.h" #elif defined(OS_WIN) @@ -82,12 +86,6 @@ #include "util/win/handle.h" #include "util/win/initial_client_data.h" #include "util/win/session_end_watcher.h" -#elif defined(OS_FUCHSIA) -#include -#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" @@ -118,6 +116,9 @@ void Usage(const base::FilePath& me) { " Address_debug_critical_section\n" " use precreated data to register initial client\n" #endif // OS_WIN +#if defined(OS_ANDROID) || defined(OS_LINUX) +" --initial-client-fd=FD a socket connected to a client.\n" +#endif // OS_ANDROID || OS_LINUX #if defined(OS_MACOSX) " --mach-service=SERVICE register SERVICE with the bootstrap server\n" #endif // OS_MACOSX @@ -133,6 +134,10 @@ void Usage(const base::FilePath& me) { " --no-periodic-tasks don't scan for new reports or prune the database\n" " --no-rate-limit don't rate limit crash uploads\n" " --no-upload-gzip don't use gzip compression when uploading\n" +#if defined(OS_ANDROID) +" --no-write-minidump-to-database\n" +" don't write minidump to database\n" +#endif // OS_ANDROID #if defined(OS_WIN) " --pipe-name=PIPE communicate with the client over PIPE\n" #endif // OS_WIN @@ -141,14 +146,31 @@ void Usage(const base::FilePath& me) { " reset the server's exception handler to default\n" #endif // OS_MACOSX #if defined(OS_LINUX) || defined(OS_ANDROID) +" --sanitization-information=SANITIZATION_INFORMATION_ADDRESS\n" +" the address of a SanitizationInformation struct.\n" +" --shared-client-connection the file descriptor provided by\n" +" --initial-client-fd is shared among multiple\n" +" clients\n" " --trace-parent-with-exception=EXCEPTION_INFORMATION_ADDRESS\n" " request a dump for the handler's parent process\n" -" --initial-client-fd=FD a socket connected to a client.\n" -" --sanitization_information=SANITIZATION_INFORMATION_ADDRESS\n" -" the address of a SanitizationInformation struct.\n" #endif // OS_LINUX || OS_ANDROID " --url=URL send crash reports to this Breakpad server URL,\n" " only if uploads are enabled for the database\n" +#if defined(OS_CHROMEOS) +" --use-cros-crash-reporter\n" +" pass crash reports to /sbin/crash_reporter\n" +" instead of storing them in the database\n" +" --minidump-dir-for-tests=TEST_MINIDUMP_DIR\n" +" causes /sbin/crash_reporter to leave dumps in\n" +" this directory instead of the normal location\n" +" --always-allow-feedback\n" +" pass the --always_allow_feedback flag to\n" +" crash_reporter, thus skipping metrics consent\n" +" checks\n" +#endif // OS_CHROMEOS +#if defined(OS_ANDROID) +" --write-minidump-to-log write minidump to log\n" +#endif // OS_ANDROID " --help display this help and exit\n" " --version output version information and exit\n", me.value().c_str()); @@ -168,8 +190,13 @@ struct Options { bool reset_own_crash_exception_port_to_system_default; #elif defined(OS_LINUX) || defined(OS_ANDROID) VMAddress exception_information_address; - int initial_client_fd; VMAddress sanitization_information_address; + int initial_client_fd; + bool shared_client_connection; +#if defined(OS_ANDROID) + bool write_minidump_to_log; + bool write_minidump_to_database; +#endif // OS_ANDROID #elif defined(OS_WIN) std::string pipe_name; InitialClientData initial_client_data; @@ -179,6 +206,11 @@ struct Options { bool periodic_tasks; bool rate_limit; bool upload_gzip; +#if defined(OS_CHROMEOS) + bool use_cros_crash_reporter = false; + base::FilePath minidump_dir_for_tests; + bool always_allow_feedback = false; +#endif // OS_CHROMEOS }; // Splits |key_value| on '=' and inserts the resulting key and value into |map|. @@ -238,8 +270,6 @@ class CallMetricsRecordNormalExit { #if defined(OS_MACOSX) || defined(OS_LINUX) || defined(OS_ANDROID) -Signals::OldActions g_old_crash_signal_handlers; - void HandleCrashSignal(int sig, siginfo_t* siginfo, void* context) { MetricsRecordExit(Metrics::LifetimeMilestone::kCrashed); @@ -274,9 +304,7 @@ void HandleCrashSignal(int sig, siginfo_t* siginfo, void* context) { } Metrics::HandlerCrashed(metrics_code); - struct sigaction* old_action = - g_old_crash_signal_handlers.ActionForSignal(sig); - Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, old_action); + Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, nullptr); } void HandleTerminateSignal(int sig, siginfo_t* siginfo, void* context) { @@ -284,13 +312,13 @@ 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 // metrics-recording handler uses signals and the Crashpad handler uses Mach // exceptions, so there’s nothing to re-enable. + // On Linux, the signal handler installed by StartHandler() restores the + // previously installed signal handler by default. } void InstallCrashHandler() { @@ -300,6 +328,8 @@ void InstallCrashHandler() { Signals::InstallTerminateHandlers(HandleTerminateSignal, 0, nullptr); } +#if defined(OS_MACOSX) + struct ResetSIGTERMTraits { static struct sigaction* InvalidValue() { return nullptr; @@ -324,21 +354,6 @@ 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) @@ -396,22 +411,6 @@ 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) { @@ -448,7 +447,7 @@ 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 defined(OS_ANDROID) if (!crashpad_client.StartHandlerAtCrash(executable_path, options.database, base::FilePath(), @@ -500,6 +499,12 @@ class ScopedStoppable { int HandlerMain(int argc, char* argv[], const UserStreamDataSources* user_stream_sources) { +#if defined(OS_CHROMEOS) + if (freopen("/var/log/chrome/chrome", "a", stderr) == nullptr) { + PLOG(ERROR) << "Failed to redirect stderr to /var/log/chrome/chrome"; + } +#endif + InstallCrashHandler(); CallMetricsRecordNormalExit metrics_record_normal_exit; @@ -518,6 +523,9 @@ int HandlerMain(int argc, #if defined(OS_WIN) kOptionInitialClientData, #endif // OS_WIN +#if defined(OS_ANDROID) || defined(OS_LINUX) + kOptionInitialClientFD, +#endif // OS_ANDROID || OS_LINUX #if defined(OS_MACOSX) kOptionMachService, #endif // OS_MACOSX @@ -529,6 +537,9 @@ int HandlerMain(int argc, kOptionNoPeriodicTasks, kOptionNoRateLimit, kOptionNoUploadGzip, +#if defined(OS_ANDROID) + kOptionNoWriteMinidumpToDatabase, +#endif // OS_ANDROID #if defined(OS_WIN) kOptionPipeName, #endif // OS_WIN @@ -536,11 +547,19 @@ int HandlerMain(int argc, kOptionResetOwnCrashExceptionPortToSystemDefault, #endif // OS_MACOSX #if defined(OS_LINUX) || defined(OS_ANDROID) - kOptionTraceParentWithException, - kOptionInitialClientFD, kOptionSanitizationInformation, + kOptionSharedClientConnection, + kOptionTraceParentWithException, #endif kOptionURL, +#if defined(OS_CHROMEOS) + kOptionUseCrosCrashReporter, + kOptionMinidumpDirForTests, + kOptionAlwaysAllowFeedback, +#endif // OS_CHROMEOS +#if defined(OS_ANDROID) + kOptionWriteMinidumpToLog, +#endif // OS_ANDROID // Standard options. kOptionHelp = -2, @@ -559,6 +578,9 @@ int HandlerMain(int argc, nullptr, kOptionInitialClientData}, #endif // OS_MACOSX +#if defined(OS_ANDROID) || defined(OS_LINUX) + {"initial-client-fd", required_argument, nullptr, kOptionInitialClientFD}, +#endif // OS_ANDROID || OS_LINUX #if defined(OS_MACOSX) {"mach-service", required_argument, nullptr, kOptionMachService}, #endif // OS_MACOSX @@ -579,6 +601,12 @@ int HandlerMain(int argc, {"no-periodic-tasks", no_argument, nullptr, kOptionNoPeriodicTasks}, {"no-rate-limit", no_argument, nullptr, kOptionNoRateLimit}, {"no-upload-gzip", no_argument, nullptr, kOptionNoUploadGzip}, +#if defined(OS_ANDROID) + {"no-write-minidump-to-database", + no_argument, + nullptr, + kOptionNoWriteMinidumpToDatabase}, +#endif // OS_ANDROID #if defined(OS_WIN) {"pipe-name", required_argument, nullptr, kOptionPipeName}, #endif // OS_WIN @@ -589,17 +617,37 @@ int HandlerMain(int argc, kOptionResetOwnCrashExceptionPortToSystemDefault}, #endif // OS_MACOSX #if defined(OS_LINUX) || defined(OS_ANDROID) - {"trace-parent-with-exception", - required_argument, - nullptr, - kOptionTraceParentWithException}, - {"initial-client-fd", required_argument, nullptr, kOptionInitialClientFD}, {"sanitization-information", required_argument, nullptr, kOptionSanitizationInformation}, + {"shared-client-connection", + no_argument, + nullptr, + kOptionSharedClientConnection}, + {"trace-parent-with-exception", + required_argument, + nullptr, + kOptionTraceParentWithException}, #endif // OS_LINUX || OS_ANDROID {"url", required_argument, nullptr, kOptionURL}, +#if defined(OS_CHROMEOS) + {"use-cros-crash-reporter", + no_argument, + nullptr, + kOptionUseCrosCrashReporter}, + {"minidump-dir-for-tests", + required_argument, + nullptr, + kOptionMinidumpDirForTests}, + {"always-allow-feedback", + no_argument, + nullptr, + kOptionAlwaysAllowFeedback}, +#endif // OS_CHROMEOS +#if defined(OS_ANDROID) + {"write-minidump-to-log", no_argument, nullptr, kOptionWriteMinidumpToLog}, +#endif // OS_ANDROID {"help", no_argument, nullptr, kOptionHelp}, {"version", no_argument, nullptr, kOptionVersion}, {nullptr, 0, nullptr, 0}, @@ -610,13 +658,14 @@ int HandlerMain(int argc, options.handshake_fd = -1; #endif options.identify_client_via_url = true; +#if defined(OS_LINUX) || defined(OS_ANDROID) + options.initial_client_fd = kInvalidFileHandle; +#endif options.periodic_tasks = true; options.rate_limit = true; options.upload_gzip = true; -#if defined(OS_LINUX) || defined(OS_ANDROID) - options.exception_information_address = 0; - options.initial_client_fd = kInvalidFileHandle; - options.sanitization_information_address = 0; +#if defined(OS_ANDROID) + options.write_minidump_to_database = true; #endif int opt; @@ -658,6 +707,15 @@ int HandlerMain(int argc, break; } #endif // OS_WIN +#if defined(OS_ANDROID) || defined(OS_LINUX) + case kOptionInitialClientFD: { + if (!base::StringToInt(optarg, &options.initial_client_fd)) { + ToolSupport::UsageHint(me, "failed to parse --initial-client-fd"); + return ExitFailure(); + } + break; + } +#endif // OS_ANDROID || OS_LINUX case kOptionMetrics: { options.metrics_dir = base::FilePath( ToolSupport::CommandLineArgumentToFilePathStringType(optarg)); @@ -695,6 +753,12 @@ int HandlerMain(int argc, options.upload_gzip = false; break; } +#if defined(OS_ANDROID) + case kOptionNoWriteMinidumpToDatabase: { + options.write_minidump_to_database = false; + break; + } +#endif // OS_ANDROID #if defined(OS_WIN) case kOptionPipeName: { options.pipe_name = optarg; @@ -708,21 +772,6 @@ int HandlerMain(int argc, } #endif // OS_MACOSX #if defined(OS_LINUX) || defined(OS_ANDROID) - case kOptionTraceParentWithException: { - if (!StringToNumber(optarg, &options.exception_information_address)) { - ToolSupport::UsageHint( - me, "failed to parse --trace-parent-with-exception"); - return ExitFailure(); - } - break; - } - case kOptionInitialClientFD: { - if (!base::StringToInt(optarg, &options.initial_client_fd)) { - ToolSupport::UsageHint(me, "failed to parse --initial-client-fd"); - return ExitFailure(); - } - break; - } case kOptionSanitizationInformation: { if (!StringToNumber(optarg, &options.sanitization_information_address)) { @@ -732,11 +781,44 @@ int HandlerMain(int argc, } break; } + case kOptionSharedClientConnection: { + options.shared_client_connection = true; + break; + } + case kOptionTraceParentWithException: { + if (!StringToNumber(optarg, &options.exception_information_address)) { + ToolSupport::UsageHint( + me, "failed to parse --trace-parent-with-exception"); + return ExitFailure(); + } + break; + } #endif // OS_LINUX || OS_ANDROID case kOptionURL: { options.url = optarg; break; } +#if defined(OS_CHROMEOS) + case kOptionUseCrosCrashReporter: { + options.use_cros_crash_reporter = true; + break; + } + case kOptionMinidumpDirForTests: { + options.minidump_dir_for_tests = base::FilePath( + ToolSupport::CommandLineArgumentToFilePathStringType(optarg)); + break; + } + case kOptionAlwaysAllowFeedback: { + options.always_allow_feedback = true; + break; + } +#endif // OS_CHROMEOS +#if defined(OS_ANDROID) + case kOptionWriteMinidumpToLog: { + options.write_minidump_to_log = true; + break; + } +#endif // OS_ANDROID case kOptionHelp: { Usage(me); MetricsRecordExit(Metrics::LifetimeMilestone::kExitedEarly); @@ -781,7 +863,7 @@ int HandlerMain(int argc, if (!options.exception_information_address && options.initial_client_fd == kInvalidFileHandle) { ToolSupport::UsageHint( - me, "--trace-parent-with-exception or --initial_client_fd is required"); + me, "--trace-parent-with-exception or --initial-client-fd is required"); return ExitFailure(); } if (options.sanitization_information_address && @@ -791,6 +873,20 @@ int HandlerMain(int argc, "--sanitization_information requires --trace-parent-with-exception"); return ExitFailure(); } + if (options.shared_client_connection && + options.initial_client_fd == kInvalidFileHandle) { + ToolSupport::UsageHint( + me, "--shared-client-connection requires --initial-client-fd"); + return ExitFailure(); + } +#if defined(OS_ANDROID) + if (!options.write_minidump_to_log && !options.write_minidump_to_database) { + ToolSupport::UsageHint(me, + "--no_write_minidump_to_database is required to use " + "with --write_minidump_to_log."); + ExitFailure(); + } +#endif // OS_ANDROID #endif // OS_MACOSX if (options.database.empty()) { @@ -856,24 +952,62 @@ int HandlerMain(int argc, upload_thread.Get()->Start(); } - CrashReportExceptionHandler exception_handler( +#if defined(OS_LINUX) || defined(OS_ANDROID) + std::unique_ptr exception_handler; +#else + std::unique_ptr exception_handler; +#endif + +#if defined(OS_CHROMEOS) + if (options.use_cros_crash_reporter) { + auto cros_handler = std::make_unique( + database.get(), + &options.annotations, + user_stream_sources); + + if (!options.minidump_dir_for_tests.empty()) { + cros_handler->SetDumpDir(options.minidump_dir_for_tests); + } + + if (options.always_allow_feedback) { + cros_handler->SetAlwaysAllowFeedback(); + } + + exception_handler = std::move(cros_handler); + } else { + exception_handler = std::make_unique( + database.get(), + static_cast(upload_thread.Get()), + &options.annotations, + true, + false, + user_stream_sources); + } +#else + exception_handler = std::make_unique( 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 +#if defined(OS_ANDROID) + options.write_minidump_to_database, + options.write_minidump_to_log, +#endif // OS_ANDROID +#if defined(OS_LINUX) + true, + false, +#endif // OS_LINUX user_stream_sources); +#endif // OS_CHROMEOS - #if defined(OS_LINUX) || defined(OS_ANDROID) +#if defined(OS_LINUX) || defined(OS_ANDROID) if (options.exception_information_address) { - ClientInformation info; + ExceptionHandlerProtocol::ClientInformation info; info.exception_information_address = options.exception_information_address; info.sanitization_information_address = options.sanitization_information_address; - return exception_handler.HandleException(getppid(), info) ? EXIT_SUCCESS - : ExitFailure(); + return exception_handler->HandleException(getppid(), geteuid(), info) + ? EXIT_SUCCESS + : ExitFailure(); } #endif // OS_LINUX || OS_ANDROID @@ -937,27 +1071,6 @@ 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 @@ -978,17 +1091,18 @@ int HandlerMain(int argc, #if defined(OS_WIN) if (options.initial_client_data.IsValid()) { exception_handler_server.InitializeWithInheritedDataForInitialClient( - options.initial_client_data, &exception_handler); + options.initial_client_data, exception_handler.get()); } #elif defined(OS_LINUX) || defined(OS_ANDROID) if (options.initial_client_fd == kInvalidFileHandle || - !exception_handler_server.InitializeWithClient( - ScopedFileHandle(options.initial_client_fd))) { + !exception_handler_server.InitializeWithClient( + ScopedFileHandle(options.initial_client_fd), + options.shared_client_connection)) { return ExitFailure(); } #endif // OS_WIN - exception_handler_server.Run(&exception_handler); + exception_handler_server.Run(exception_handler.get()); return EXIT_SUCCESS; } diff --git a/handler/handler_main.h b/handler/handler_main.h index af01dbe7..25265411 100644 --- a/handler/handler_main.h +++ b/handler/handler_main.h @@ -15,6 +15,7 @@ #ifndef CRASHPAD_HANDLER_HANDLER_MAIN_H_ #define CRASHPAD_HANDLER_HANDLER_MAIN_H_ +#include "build/build_config.h" #include "handler/user_stream_data_source.h" namespace crashpad { @@ -34,6 +35,16 @@ int HandlerMain(int argc, char* argv[], const UserStreamDataSources* user_stream_sources); +#if defined(OS_ANDROID) +//! \brief The `main()` entry point for Android libraries. +//! +//! This symbol is the entry point for crashpad when it is dynamically loaded +//! using /system/bin/linker. +//! +//! \sa CrashpadClient::StartHandlerWithLinkerAtCrash() +extern "C" int CrashpadHandlerMain(int argc, char* argv[]); +#endif + } // namespace crashpad #endif // CRASHPAD_HANDLER_HANDLER_MAIN_H_ diff --git a/handler/linux/capture_snapshot.cc b/handler/linux/capture_snapshot.cc new file mode 100644 index 00000000..00540295 --- /dev/null +++ b/handler/linux/capture_snapshot.cc @@ -0,0 +1,119 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "handler/linux/capture_snapshot.h" + +#include + +#include "snapshot/crashpad_info_client_options.h" +#include "snapshot/sanitized/sanitization_information.h" +#include "util/misc/metrics.h" +#include "util/misc/tri_state.h" + +namespace crashpad { + +bool CaptureSnapshot( + PtraceConnection* connection, + const ExceptionHandlerProtocol::ClientInformation& info, + const std::map& process_annotations, + uid_t client_uid, + VMAddress requesting_thread_stack_address, + pid_t* requesting_thread_id, + std::unique_ptr* snapshot, + std::unique_ptr* sanitized_snapshot) { + std::unique_ptr process_snapshot( + new ProcessSnapshotLinux()); + if (!process_snapshot->Initialize(connection)) { + Metrics::ExceptionCaptureResult(Metrics::CaptureResult::kSnapshotFailed); + return false; + } + + pid_t local_requesting_thread_id = -1; + if (requesting_thread_stack_address) { + local_requesting_thread_id = process_snapshot->FindThreadWithStackAddress( + requesting_thread_stack_address); + } + + if (requesting_thread_id) { + *requesting_thread_id = local_requesting_thread_id; + } + + if (!process_snapshot->InitializeException(info.exception_information_address, + local_requesting_thread_id)) { + Metrics::ExceptionCaptureResult( + Metrics::CaptureResult::kExceptionInitializationFailed); + return false; + } + + Metrics::ExceptionCode(process_snapshot->Exception()->Exception()); + + CrashpadInfoClientOptions client_options; + process_snapshot->GetCrashpadOptions(&client_options); + if (client_options.crashpad_handler_behavior == TriState::kDisabled) { + return false; + } + + for (auto& p : process_annotations) { + process_snapshot->AddAnnotation(p.first, p.second); + } + + if (info.sanitization_information_address) { + SanitizationInformation sanitization_info; + ProcessMemoryRange range; + if (!range.Initialize(connection->Memory(), connection->Is64Bit()) || + !range.Read(info.sanitization_information_address, + sizeof(sanitization_info), + &sanitization_info)) { + Metrics::ExceptionCaptureResult( + Metrics::CaptureResult::kSanitizationInitializationFailed); + return false; + } + + auto annotations_whitelist = std::make_unique>(); + auto memory_range_whitelist = + std::make_unique>>(); + if (!ReadAnnotationsWhitelist( + range, + sanitization_info.annotations_whitelist_address, + annotations_whitelist.get()) || + !ReadMemoryRangeWhitelist( + range, + sanitization_info.memory_range_whitelist_address, + memory_range_whitelist.get())) { + Metrics::ExceptionCaptureResult( + Metrics::CaptureResult::kSanitizationInitializationFailed); + return false; + } + + std::unique_ptr sanitized( + new ProcessSnapshotSanitized()); + if (!sanitized->Initialize(process_snapshot.get(), + sanitization_info.annotations_whitelist_address + ? std::move(annotations_whitelist) + : nullptr, + std::move(memory_range_whitelist), + sanitization_info.target_module_address, + sanitization_info.sanitize_stacks)) { + Metrics::ExceptionCaptureResult( + Metrics::CaptureResult::kSkippedDueToSanitization); + return false; + } + *sanitized_snapshot = std::move(sanitized); + } + + *snapshot = std::move(process_snapshot); + return true; +} + +} // namespace crashpad diff --git a/handler/linux/capture_snapshot.h b/handler/linux/capture_snapshot.h new file mode 100644 index 00000000..78886dc5 --- /dev/null +++ b/handler/linux/capture_snapshot.h @@ -0,0 +1,67 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_HANDLER_LINUX_CAPTURE_SNAPSHOT_H_ +#define CRASHPAD_HANDLER_LINUX_CAPTURE_SNAPSHOT_H_ + +#include + +#include +#include +#include + +#include "snapshot/linux/process_snapshot_linux.h" +#include "snapshot/sanitized/process_snapshot_sanitized.h" +#include "util/linux/exception_handler_protocol.h" +#include "util/linux/ptrace_connection.h" +#include "util/misc/address_types.h" + +namespace crashpad { + +//! \brief Captures a snapshot of a client over \a connection. +//! +//! \param[in] connection A PtraceConnection to the client to snapshot. +//! \param[in] info Information about the client configuring the snapshot. +//! \param[in] process_annotations A map of annotations to insert as +//! process-level annotations into the snapshot. +//! \param[in] client_uid The client's user ID. +//! \param[in] requesting_thread_stack_address An address on the stack of the +//! thread requesting the snapshot. If \a info includes an exception +//! address, the exception will be assigned to the thread whose stack +//! address range contains this address. If 0, \a requesting_thread_id will +//! be -1. +//! \param[out] requesting_thread_id The thread ID of the thread corresponding +//! to \a requesting_thread_stack_address. Set to -1 if the thread ID could +//! not be determined. Optional. +//! \param[out] process_snapshot A snapshot of the client process, valid if this +//! function returns `true`. +//! \param[out] sanitized_snapshot A sanitized snapshot of the client process, +//! valid if this function returns `true` and sanitization was requested in +//! \a info. +//! \return `true` if \a process_snapshot was successfully created. A message +//! will be logged on failure, but not if the snapshot was skipped because +//! handling was disabled by CrashpadInfoClientOptions. +bool CaptureSnapshot( + PtraceConnection* connection, + const ExceptionHandlerProtocol::ClientInformation& info, + const std::map& process_annotations, + uid_t client_uid, + VMAddress requesting_thread_stack_address, + pid_t* requesting_thread_id, + std::unique_ptr* process_snapshot, + std::unique_ptr* sanitized_snapshot); + +} // namespace crashpad + +#endif // CRASHPAD_HANDLER_LINUX_CAPTURE_SNAPSHOT_H_ diff --git a/handler/linux/crash_report_exception_handler.cc b/handler/linux/crash_report_exception_handler.cc index 101c49f5..29f6d0db 100644 --- a/handler/linux/crash_report_exception_handler.cc +++ b/handler/linux/crash_report_exception_handler.cc @@ -14,38 +14,75 @@ #include "handler/linux/crash_report_exception_handler.h" -#include +#include +#include #include "base/logging.h" #include "client/settings.h" +#include "handler/linux/capture_snapshot.h" #include "minidump/minidump_file_writer.h" -#include "snapshot/crashpad_info_client_options.h" #include "snapshot/linux/process_snapshot_linux.h" #include "snapshot/sanitized/process_snapshot_sanitized.h" -#include "snapshot/sanitized/sanitization_information.h" +#include "util/file/file_reader.h" +#include "util/file/output_stream_file_writer.h" #include "util/linux/direct_ptrace_connection.h" #include "util/linux/ptrace_client.h" +#include "util/misc/implicit_cast.h" #include "util/misc/metrics.h" -#include "util/misc/tri_state.h" #include "util/misc/uuid.h" +#include "util/stream/base94_output_stream.h" +#include "util/stream/log_output_stream.h" +#include "util/stream/zlib_output_stream.h" namespace crashpad { +namespace { + +bool WriteMinidumpLogFromFile(FileReaderInterface* file_reader) { + ZlibOutputStream stream(ZlibOutputStream::Mode::kCompress, + std::make_unique( + Base94OutputStream::Mode::kEncode, + std::make_unique())); + FileOperationResult read_result; + do { + uint8_t buffer[4096]; + read_result = file_reader->Read(buffer, sizeof(buffer)); + if (read_result < 0) + return false; + + if (read_result > 0 && (!stream.Write(buffer, read_result))) + return false; + } while (read_result > 0); + return stream.Flush(); +} + +} // namespace + CrashReportExceptionHandler::CrashReportExceptionHandler( CrashReportDatabase* database, CrashReportUploadThread* upload_thread, const std::map* process_annotations, + bool write_minidump_to_database, + bool write_minidump_to_log, const UserStreamDataSources* user_stream_data_sources) : database_(database), upload_thread_(upload_thread), process_annotations_(process_annotations), - user_stream_data_sources_(user_stream_data_sources) {} + write_minidump_to_database_(write_minidump_to_database), + write_minidump_to_log_(write_minidump_to_log), + user_stream_data_sources_(user_stream_data_sources) { + DCHECK(write_minidump_to_database_ | write_minidump_to_log_); +} CrashReportExceptionHandler::~CrashReportExceptionHandler() = default; bool CrashReportExceptionHandler::HandleException( pid_t client_process_id, - const ClientInformation& info) { + uid_t client_uid, + const ExceptionHandlerProtocol::ClientInformation& info, + VMAddress requesting_thread_stack_address, + pid_t* requesting_thread_id, + UUID* local_report_id) { Metrics::ExceptionEncountered(); DirectPtraceConnection connection; @@ -55,13 +92,20 @@ bool CrashReportExceptionHandler::HandleException( return false; } - return HandleExceptionWithConnection(&connection, info); + return HandleExceptionWithConnection(&connection, + info, + client_uid, + requesting_thread_stack_address, + requesting_thread_id, + local_report_id); } bool CrashReportExceptionHandler::HandleExceptionWithBroker( pid_t client_process_id, - const ClientInformation& info, - int broker_sock) { + uid_t client_uid, + const ExceptionHandlerProtocol::ClientInformation& info, + int broker_sock, + UUID* local_report_id) { Metrics::ExceptionEncountered(); PtraceClient client; @@ -71,123 +115,134 @@ bool CrashReportExceptionHandler::HandleExceptionWithBroker( return false; } - return HandleExceptionWithConnection(&client, info); + return HandleExceptionWithConnection( + &client, info, client_uid, 0, nullptr, local_report_id); } bool CrashReportExceptionHandler::HandleExceptionWithConnection( PtraceConnection* connection, - const ClientInformation& info) { - ProcessSnapshotLinux process_snapshot; - if (!process_snapshot.Initialize(connection)) { - Metrics::ExceptionCaptureResult(Metrics::CaptureResult::kSnapshotFailed); + const ExceptionHandlerProtocol::ClientInformation& info, + uid_t client_uid, + VMAddress requesting_thread_stack_address, + pid_t* requesting_thread_id, + UUID* local_report_id) { + std::unique_ptr process_snapshot; + std::unique_ptr sanitized_snapshot; + if (!CaptureSnapshot(connection, + info, + *process_annotations_, + client_uid, + requesting_thread_stack_address, + requesting_thread_id, + &process_snapshot, + &sanitized_snapshot)) { return false; } - if (!process_snapshot.InitializeException( - info.exception_information_address)) { + UUID client_id; + Settings* const settings = database_->GetSettings(); + if (settings) { + // If GetSettings() or GetClientID() fails, something else will log a + // message and client_id will be left at its default value, all zeroes, + // which is appropriate. + settings->GetClientID(&client_id); + } + process_snapshot->SetClientID(client_id); + + return write_minidump_to_database_ + ? WriteMinidumpToDatabase(process_snapshot.get(), + sanitized_snapshot.get(), + write_minidump_to_log_, + local_report_id) + : WriteMinidumpToLog(process_snapshot.get(), + sanitized_snapshot.get()); +} + +bool CrashReportExceptionHandler::WriteMinidumpToDatabase( + ProcessSnapshotLinux* process_snapshot, + ProcessSnapshotSanitized* sanitized_snapshot, + bool write_minidump_to_log, + UUID* local_report_id) { + std::unique_ptr new_report; + CrashReportDatabase::OperationStatus database_status = + database_->PrepareNewCrashReport(&new_report); + if (database_status != CrashReportDatabase::kNoError) { + LOG(ERROR) << "PrepareNewCrashReport failed"; Metrics::ExceptionCaptureResult( - Metrics::CaptureResult::kExceptionInitializationFailed); + Metrics::CaptureResult::kPrepareNewCrashReportFailed); return false; } - Metrics::ExceptionCode(process_snapshot.Exception()->Exception()); + process_snapshot->SetReportID(new_report->ReportID()); - CrashpadInfoClientOptions client_options; - process_snapshot.GetCrashpadOptions(&client_options); - if (client_options.crashpad_handler_behavior != TriState::kDisabled) { - UUID client_id; - Settings* const settings = database_->GetSettings(); - if (settings) { - // If GetSettings() or GetClientID() fails, something else will log a - // message and client_id will be left at its default value, all zeroes, - // which is appropriate. - settings->GetClientID(&client_id); + ProcessSnapshot* snapshot = + sanitized_snapshot ? implicit_cast(sanitized_snapshot) + : implicit_cast(process_snapshot); + + MinidumpFileWriter minidump; + minidump.InitializeFromSnapshot(snapshot); + AddUserExtensionStreams(user_stream_data_sources_, snapshot, &minidump); + + if (!minidump.WriteEverything(new_report->Writer())) { + LOG(ERROR) << "WriteEverything failed"; + Metrics::ExceptionCaptureResult( + Metrics::CaptureResult::kMinidumpWriteFailed); + return false; + } + + bool write_minidump_to_log_succeed = false; + if (write_minidump_to_log) { + if (auto* file_reader = new_report->Reader()) { + if (WriteMinidumpLogFromFile(file_reader)) + write_minidump_to_log_succeed = true; + else + LOG(ERROR) << "WriteMinidumpLogFromFile failed"; } + } - process_snapshot.SetClientID(client_id); - process_snapshot.SetAnnotationsSimpleMap(*process_annotations_); + UUID uuid; + database_status = + database_->FinishedWritingCrashReport(std::move(new_report), &uuid); + if (database_status != CrashReportDatabase::kNoError) { + LOG(ERROR) << "FinishedWritingCrashReport failed"; + Metrics::ExceptionCaptureResult( + Metrics::CaptureResult::kFinishedWritingCrashReportFailed); + return false; + } - std::unique_ptr new_report; - CrashReportDatabase::OperationStatus database_status = - database_->PrepareNewCrashReport(&new_report); - if (database_status != CrashReportDatabase::kNoError) { - LOG(ERROR) << "PrepareNewCrashReport failed"; - Metrics::ExceptionCaptureResult( - Metrics::CaptureResult::kPrepareNewCrashReportFailed); - return false; - } + if (upload_thread_) { + upload_thread_->ReportPending(uuid); + } - process_snapshot.SetReportID(new_report->ReportID()); - - ProcessSnapshot* snapshot = nullptr; - ProcessSnapshotSanitized sanitized; - std::vector whitelist; - if (info.sanitization_information_address) { - SanitizationInformation sanitization_info; - ProcessMemoryRange range; - if (!range.Initialize(connection->Memory(), connection->Is64Bit()) || - !range.Read(info.sanitization_information_address, - sizeof(sanitization_info), - &sanitization_info)) { - Metrics::ExceptionCaptureResult( - Metrics::CaptureResult::kSanitizationInitializationFailed); - return false; - } - - if (sanitization_info.annotations_whitelist_address && - !ReadAnnotationsWhitelist( - range, - sanitization_info.annotations_whitelist_address, - &whitelist)) { - Metrics::ExceptionCaptureResult( - Metrics::CaptureResult::kSanitizationInitializationFailed); - return false; - } - - if (!sanitized.Initialize(&process_snapshot, - sanitization_info.annotations_whitelist_address - ? &whitelist - : nullptr, - sanitization_info.target_module_address, - sanitization_info.sanitize_stacks)) { - Metrics::ExceptionCaptureResult( - Metrics::CaptureResult::kSkippedDueToSanitization); - return true; - } - - snapshot = &sanitized; - } else { - snapshot = &process_snapshot; - } - - MinidumpFileWriter minidump; - minidump.InitializeFromSnapshot(snapshot); - AddUserExtensionStreams(user_stream_data_sources_, snapshot, &minidump); - - if (!minidump.WriteEverything(new_report->Writer())) { - LOG(ERROR) << "WriteEverything failed"; - Metrics::ExceptionCaptureResult( - Metrics::CaptureResult::kMinidumpWriteFailed); - return false; - } - - UUID uuid; - database_status = - database_->FinishedWritingCrashReport(std::move(new_report), &uuid); - if (database_status != CrashReportDatabase::kNoError) { - LOG(ERROR) << "FinishedWritingCrashReport failed"; - Metrics::ExceptionCaptureResult( - Metrics::CaptureResult::kFinishedWritingCrashReportFailed); - return false; - } - - if (upload_thread_) { - upload_thread_->ReportPending(uuid); - } + if (local_report_id != nullptr) { + *local_report_id = uuid; } Metrics::ExceptionCaptureResult(Metrics::CaptureResult::kSuccess); - return true; + + return write_minidump_to_log ? write_minidump_to_log_succeed : true; +} + +bool CrashReportExceptionHandler::WriteMinidumpToLog( + ProcessSnapshotLinux* process_snapshot, + ProcessSnapshotSanitized* sanitized_snapshot) { + ProcessSnapshot* snapshot = + sanitized_snapshot ? implicit_cast(sanitized_snapshot) + : implicit_cast(process_snapshot); + MinidumpFileWriter minidump; + minidump.InitializeFromSnapshot(snapshot); + AddUserExtensionStreams(user_stream_data_sources_, snapshot, &minidump); + + OutputStreamFileWriter writer(std::make_unique( + ZlibOutputStream::Mode::kCompress, + std::make_unique( + Base94OutputStream::Mode::kEncode, + std::make_unique()))); + if (!minidump.WriteMinidump(&writer, false /* allow_seek */)) { + LOG(ERROR) << "WriteMinidump failed"; + return false; + } + return writer.Flush(); } } // namespace crashpad diff --git a/handler/linux/crash_report_exception_handler.h b/handler/linux/crash_report_exception_handler.h index 24d5995a..83d07c0b 100644 --- a/handler/linux/crash_report_exception_handler.h +++ b/handler/linux/crash_report_exception_handler.h @@ -26,9 +26,13 @@ #include "util/linux/exception_handler_protocol.h" #include "util/linux/ptrace_connection.h" #include "util/misc/address_types.h" +#include "util/misc/uuid.h" namespace crashpad { +class ProcessSnapshotLinux; +class ProcessSnapshotSanitized; + //! \brief An exception handler that writes crash reports for exceptions //! to a CrashReportDatabase. class CrashReportExceptionHandler : public ExceptionHandlerServer::Delegate { @@ -49,6 +53,10 @@ class CrashReportExceptionHandler : public ExceptionHandlerServer::Delegate { //! To interoperate with Breakpad servers, the recommended practice is to //! specify values for the `"prod"` and `"ver"` keys as process //! annotations. + //! \param[in] write_minidump_to_database Whether the minidump shall be + //! written to database. + //! \param[in] write_minidump_to_log Whether the minidump shall be written to + //! log. //! \param[in] user_stream_data_sources Data sources to be used to extend //! crash reports. For each crash report that is written, the data sources //! are called in turn. These data sources may contribute additional @@ -57,26 +65,49 @@ class CrashReportExceptionHandler : public ExceptionHandlerServer::Delegate { CrashReportDatabase* database, CrashReportUploadThread* upload_thread, const std::map* process_annotations, + bool write_minidump_to_database, + bool write_minidump_to_log, const UserStreamDataSources* user_stream_data_sources); - ~CrashReportExceptionHandler(); + ~CrashReportExceptionHandler() override; // ExceptionHandlerServer::Delegate: bool HandleException(pid_t client_process_id, - const ClientInformation& info) override; + uid_t client_uid, + const ExceptionHandlerProtocol::ClientInformation& info, + VMAddress requesting_thread_stack_address = 0, + pid_t* requesting_thread_id = nullptr, + UUID* local_report_id = nullptr) override; - bool HandleExceptionWithBroker(pid_t client_process_id, - const ClientInformation& info, - int broker_sock) override; + bool HandleExceptionWithBroker( + pid_t client_process_id, + uid_t client_uid, + const ExceptionHandlerProtocol::ClientInformation& info, + int broker_sock, + UUID* local_report_id = nullptr) override; private: - bool HandleExceptionWithConnection(PtraceConnection* connection, - const ClientInformation& info); + bool HandleExceptionWithConnection( + PtraceConnection* connection, + const ExceptionHandlerProtocol::ClientInformation& info, + uid_t client_uid, + VMAddress requesting_thread_stack_address, + pid_t* requesting_thread_id, + UUID* local_report_id = nullptr); + + bool WriteMinidumpToDatabase(ProcessSnapshotLinux* process_snapshot, + ProcessSnapshotSanitized* sanitized_snapshot, + bool write_minidump_to_log, + UUID* local_report_id); + bool WriteMinidumpToLog(ProcessSnapshotLinux* process_snapshot, + ProcessSnapshotSanitized* sanitized_snapshot); CrashReportDatabase* database_; // weak CrashReportUploadThread* upload_thread_; // weak const std::map* process_annotations_; // weak + bool write_minidump_to_database_; + bool write_minidump_to_log_; const UserStreamDataSources* user_stream_data_sources_; // weak DISALLOW_COPY_AND_ASSIGN(CrashReportExceptionHandler); diff --git a/handler/linux/cros_crash_report_exception_handler.cc b/handler/linux/cros_crash_report_exception_handler.cc new file mode 100644 index 00000000..92bc93e0 --- /dev/null +++ b/handler/linux/cros_crash_report_exception_handler.cc @@ -0,0 +1,286 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "handler/linux/cros_crash_report_exception_handler.h" + +#include + +#include "base/logging.h" +#include "client/settings.h" +#include "handler/linux/capture_snapshot.h" +#include "handler/minidump_to_upload_parameters.h" +#include "minidump/minidump_file_writer.h" +#include "snapshot/linux/process_snapshot_linux.h" +#include "snapshot/minidump/process_snapshot_minidump.h" +#include "snapshot/sanitized/process_snapshot_sanitized.h" +#include "util/file/file_writer.h" +#include "util/linux/direct_ptrace_connection.h" +#include "util/linux/ptrace_client.h" +#include "util/misc/metrics.h" +#include "util/misc/uuid.h" +#include "util/posix/double_fork_and_exec.h" + +namespace crashpad { + +namespace { + +// Returns the process name for a pid. +const std::string GetProcessNameFromPid(pid_t pid) { + // Symlink to process binary is at /proc/###/exe. + std::string link_path = "/proc/" + std::to_string(pid) + "/exe"; + + constexpr int kMaxSize = 4096; + std::unique_ptr buf(new char[kMaxSize]); + ssize_t size = readlink(link_path.c_str(), buf.get(), kMaxSize); + std::string result; + if (size < 0) { + PLOG(ERROR) << "Failed to readlink " << link_path; + } else { + result.assign(buf.get(), size); + size_t last_slash_pos = result.rfind('/'); + if (last_slash_pos != std::string::npos) { + result = result.substr(last_slash_pos + 1); + } + } + return result; +} + +bool WriteAnnotationsAndMinidump( + const std::map& parameters, + MinidumpFileWriter& minidump, + FileWriter& file_writer) { + for (const auto& kv : parameters) { + if (kv.first.find(':') != std::string::npos) { + LOG(ERROR) << "Annotation key cannot have ':' in it " << kv.first; + return false; + } + if (!file_writer.Write(kv.first.c_str(), strlen(kv.first.c_str()))) { + return false; + } + if (!file_writer.Write(":", 1)) { + return false; + } + size_t value_size = strlen(kv.second.c_str()); + std::string value_size_str = std::to_string(value_size); + if (!file_writer.Write(value_size_str.c_str(), value_size_str.size())) { + return false; + } + if (!file_writer.Write(":", 1)) { + return false; + } + if (!file_writer.Write(kv.second.c_str(), strlen(kv.second.c_str()))) { + return false; + } + } + + static constexpr char kMinidumpName[] = + "upload_file_minidump\"; filename=\"dump\":"; + if (!file_writer.Write(kMinidumpName, sizeof(kMinidumpName) - 1)) { + return false; + } + crashpad::FileOffset dump_size_start_offset = file_writer.Seek(0, SEEK_CUR); + if (dump_size_start_offset < 0) { + LOG(ERROR) << "Failed to get minidump size start offset"; + return false; + } + static constexpr char kMinidumpLengthFilling[] = "00000000000000000000:"; + if (!file_writer.Write(kMinidumpLengthFilling, + sizeof(kMinidumpLengthFilling) - 1)) { + return false; + } + crashpad::FileOffset dump_start_offset = file_writer.Seek(0, SEEK_CUR); + if (dump_start_offset < 0) { + LOG(ERROR) << "Failed to get minidump start offset"; + return false; + } + if (!minidump.WriteEverything(&file_writer)) { + return false; + } + crashpad::FileOffset dump_end_offset = file_writer.Seek(0, SEEK_CUR); + if (dump_end_offset < 0) { + LOG(ERROR) << "Failed to get minidump end offset"; + return false; + } + + size_t dump_data_size = dump_end_offset - dump_start_offset; + std::string dump_data_size_str = std::to_string(dump_data_size); + file_writer.Seek(dump_size_start_offset + strlen(kMinidumpLengthFilling) - 1 - + dump_data_size_str.size(), + SEEK_SET); + if (!file_writer.Write(dump_data_size_str.c_str(), + dump_data_size_str.size())) { + return false; + } + return true; +} + +} // namespace + +CrosCrashReportExceptionHandler::CrosCrashReportExceptionHandler( + CrashReportDatabase* database, + const std::map* process_annotations, + const UserStreamDataSources* user_stream_data_sources) + : database_(database), + process_annotations_(process_annotations), + user_stream_data_sources_(user_stream_data_sources), + always_allow_feedback_(false) {} + +CrosCrashReportExceptionHandler::~CrosCrashReportExceptionHandler() = default; + +bool CrosCrashReportExceptionHandler::HandleException( + pid_t client_process_id, + uid_t client_uid, + const ExceptionHandlerProtocol::ClientInformation& info, + VMAddress requesting_thread_stack_address, + pid_t* requesting_thread_id, + UUID* local_report_id) { + Metrics::ExceptionEncountered(); + + DirectPtraceConnection connection; + if (!connection.Initialize(client_process_id)) { + Metrics::ExceptionCaptureResult( + Metrics::CaptureResult::kDirectPtraceFailed); + return false; + } + + return HandleExceptionWithConnection(&connection, + info, + client_uid, + requesting_thread_stack_address, + requesting_thread_id, + local_report_id); +} + +bool CrosCrashReportExceptionHandler::HandleExceptionWithBroker( + pid_t client_process_id, + uid_t client_uid, + const ExceptionHandlerProtocol::ClientInformation& info, + int broker_sock, + UUID* local_report_id) { + Metrics::ExceptionEncountered(); + + PtraceClient client; + if (!client.Initialize(broker_sock, client_process_id)) { + Metrics::ExceptionCaptureResult( + Metrics::CaptureResult::kBrokeredPtraceFailed); + return false; + } + + return HandleExceptionWithConnection( + &client, info, client_uid, 0, nullptr, local_report_id); +} + +bool CrosCrashReportExceptionHandler::HandleExceptionWithConnection( + PtraceConnection* connection, + const ExceptionHandlerProtocol::ClientInformation& info, + uid_t client_uid, + VMAddress requesting_thread_stack_address, + pid_t* requesting_thread_id, + UUID* local_report_id) { + std::unique_ptr process_snapshot; + std::unique_ptr sanitized_snapshot; + if (!CaptureSnapshot(connection, + info, + *process_annotations_, + client_uid, + requesting_thread_stack_address, + requesting_thread_id, + &process_snapshot, + &sanitized_snapshot)) { + return false; + } + + UUID client_id; + Settings* const settings = database_->GetSettings(); + if (settings) { + // If GetSettings() or GetClientID() fails, something else will log a + // message and client_id will be left at its default value, all zeroes, + // which is appropriate. + settings->GetClientID(&client_id); + } + process_snapshot->SetClientID(client_id); + + UUID uuid; + uuid.InitializeWithNew(); + process_snapshot->SetReportID(uuid); + + ProcessSnapshot* snapshot = + sanitized_snapshot + ? implicit_cast(sanitized_snapshot.get()) + : implicit_cast(process_snapshot.get()); + + MinidumpFileWriter minidump; + minidump.InitializeFromSnapshot(snapshot); + AddUserExtensionStreams(user_stream_data_sources_, snapshot, &minidump); + + FileWriter file_writer; + if (!file_writer.OpenMemfd(base::FilePath("minidump"))) { + Metrics::ExceptionCaptureResult(Metrics::CaptureResult::kOpenMemfdFailed); + return false; + } + + std::map parameters = + BreakpadHTTPFormParametersFromMinidump(snapshot); + // Used to differentiate between breakpad and crashpad while the switch is + // ramping up. + parameters.emplace("crash_library", "crashpad"); + + if (!WriteAnnotationsAndMinidump(parameters, minidump, file_writer)) { + Metrics::ExceptionCaptureResult( + Metrics::CaptureResult::kMinidumpWriteFailed); + return false; + } + + // CrOS uses crash_reporter instead of Crashpad to report crashes. + // crash_reporter needs to know the pid and uid of the crashing process. + std::vector argv({"/sbin/crash_reporter"}); + + argv.push_back("--chrome_memfd=" + std::to_string(file_writer.fd())); + argv.push_back("--pid=" + std::to_string(*requesting_thread_id)); + argv.push_back("--uid=" + std::to_string(client_uid)); + std::string process_name = GetProcessNameFromPid(*requesting_thread_id); + argv.push_back("--exe=" + (process_name.empty() ? "chrome" : process_name)); + + if (info.crash_loop_before_time != 0) { + argv.push_back("--crash_loop_before=" + + std::to_string(info.crash_loop_before_time)); + } + if (!dump_dir_.empty()) { + argv.push_back("--chrome_dump_dir=" + dump_dir_.value()); + } + if (always_allow_feedback_) { + argv.push_back("--always_allow_feedback"); + } + + if (!DoubleForkAndExec(argv, + nullptr /* envp */, + file_writer.fd() /* preserve_fd */, + false /* use_path */, + nullptr /* child_function */)) { + LOG(ERROR) << "DoubleForkAndExec failed"; + Metrics::ExceptionCaptureResult( + Metrics::CaptureResult::kFinishedWritingCrashReportFailed); + return false; + } + + if (local_report_id != nullptr) { + *local_report_id = uuid; + } + LOG(INFO) << "Successfully wrote report " << uuid.ToString(); + + Metrics::ExceptionCaptureResult(Metrics::CaptureResult::kSuccess); + return true; +} + +} // namespace crashpad diff --git a/handler/linux/cros_crash_report_exception_handler.h b/handler/linux/cros_crash_report_exception_handler.h new file mode 100644 index 00000000..0b145448 --- /dev/null +++ b/handler/linux/cros_crash_report_exception_handler.h @@ -0,0 +1,101 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_HANDLER_LINUX_CROS_CRASH_REPORT_EXCEPTION_HANDLER_H_ +#define CRASHPAD_HANDLER_LINUX_CROS_CRASH_REPORT_EXCEPTION_HANDLER_H_ + +#include +#include + +#include "base/macros.h" +#include "client/crash_report_database.h" +#include "handler/linux/exception_handler_server.h" +#include "handler/user_stream_data_source.h" +#include "util/linux/exception_handler_protocol.h" +#include "util/linux/ptrace_connection.h" +#include "util/misc/address_types.h" +#include "util/misc/uuid.h" + +namespace crashpad { + +//! \brief An exception handler that writes crash reports to the ChromeOS +//! crash_reporter. +class CrosCrashReportExceptionHandler + : public ExceptionHandlerServer::Delegate { + public: + //! \brief Creates a new object that will pass reports to + //! `/sbin/crash_reporter`. + //! + //! \param[in] database The database that supplies settings for this client. + //! This object does not write its reports to this database. + //! \param[in] process_annotations A map of annotations to insert as + //! process-level annotations into each crash report that is written. Do + //! not confuse this with module-level annotations, which are under the + //! control of the crashing process, and are used to implement Chrome’s + //! “crash keys.” Process-level annotations are those that are beyond the + //! control of the crashing process, which must reliably be set even if + //! the process crashes before it’s able to establish its own annotations. + //! To interoperate with Breakpad servers, the recommended practice is to + //! specify values for the `"prod"` and `"ver"` keys as process + //! annotations. + //! \param[in] user_stream_data_sources Data sources to be used to extend + //! crash reports. For each crash report that is written, the data sources + //! are called in turn. These data sources may contribute additional + //! minidump streams. `nullptr` if not required. + CrosCrashReportExceptionHandler( + CrashReportDatabase* database, + const std::map* process_annotations, + const UserStreamDataSources* user_stream_data_sources); + + ~CrosCrashReportExceptionHandler() override; + + // ExceptionHandlerServer::Delegate: + + bool HandleException(pid_t client_process_id, + uid_t client_uid, + const ExceptionHandlerProtocol::ClientInformation& info, + VMAddress requesting_thread_stack_address = 0, + pid_t* requesting_thread_id = nullptr, + UUID* local_report_id = nullptr) override; + + bool HandleExceptionWithBroker( + pid_t client_process_id, + uid_t client_uid, + const ExceptionHandlerProtocol::ClientInformation& info, + int broker_sock, + UUID* local_report_id = nullptr) override; + + void SetDumpDir(const base::FilePath& dump_dir) { dump_dir_ = dump_dir; } + void SetAlwaysAllowFeedback() { always_allow_feedback_ = true; } + private: + bool HandleExceptionWithConnection( + PtraceConnection* connection, + const ExceptionHandlerProtocol::ClientInformation& info, + uid_t client_uid, + VMAddress requesting_thread_stack_address, + pid_t* requesting_thread_id, + UUID* local_report_id = nullptr); + + CrashReportDatabase* database_; // weak + const std::map* process_annotations_; // weak + const UserStreamDataSources* user_stream_data_sources_; // weak + base::FilePath dump_dir_; + bool always_allow_feedback_; + + DISALLOW_COPY_AND_ASSIGN(CrosCrashReportExceptionHandler); +}; + +} // namespace crashpad + +#endif // CRASHPAD_HANDLER_LINUX_CROS_CRASH_REPORT_EXCEPTION_HANDLER_H_ diff --git a/handler/linux/exception_handler_server.cc b/handler/linux/exception_handler_server.cc index 748bf6e9..c9fbb0ad 100644 --- a/handler/linux/exception_handler_server.cc +++ b/handler/linux/exception_handler_server.cc @@ -15,10 +15,11 @@ #include "handler/linux/exception_handler_server.h" #include -#include +#include #include #include #include +#include #include #include @@ -31,6 +32,8 @@ #include "build/build_config.h" #include "util/file/file_io.h" #include "util/file/filesystem.h" +#include "util/linux/proc_task_reader.h" +#include "util/linux/socket.h" #include "util/misc/as_underlying_type.h" namespace crashpad { @@ -90,54 +93,96 @@ PtraceScope GetPtraceScope() { } bool HaveCapSysPtrace() { - struct __user_cap_header_struct cap_header = {}; - struct __user_cap_data_struct cap_data = {}; - + __user_cap_header_struct cap_header; cap_header.pid = getpid(); + cap_header.version = _LINUX_CAPABILITY_VERSION_3; - if (capget(&cap_header, &cap_data) != 0) { + __user_cap_data_struct cap_data[_LINUX_CAPABILITY_U32S_3]; + if (syscall(SYS_capget, &cap_header, &cap_data) != 0) { PLOG(ERROR) << "capget"; + LOG_IF(ERROR, errno == EINVAL) << "cap_header.version " << std::hex + << cap_header.version; return false; } - if (cap_header.version != _LINUX_CAPABILITY_VERSION_3) { - LOG(ERROR) << "Unexpected capability version " << std::hex - << cap_header.version; - return false; - } - - return (cap_data.effective & (1 << CAP_SYS_PTRACE)) != 0; + return (cap_data[CAP_TO_INDEX(CAP_SYS_PTRACE)].effective & + CAP_TO_MASK(CAP_SYS_PTRACE)) != 0; } -bool SendMessageToClient(int client_sock, ServerToClientMessage::Type type) { - ServerToClientMessage message = {}; +bool SendMessageToClient( + int client_sock, + ExceptionHandlerProtocol::ServerToClientMessage::Type type) { + ExceptionHandlerProtocol::ServerToClientMessage message = {}; message.type = type; - if (type == ServerToClientMessage::kTypeSetPtracer) { + if (type == + ExceptionHandlerProtocol::ServerToClientMessage::kTypeSetPtracer) { message.pid = getpid(); } return LoggingWriteFile(client_sock, &message, sizeof(message)); } +int tgkill(pid_t pid, pid_t tid, int signo) { + return syscall(SYS_tgkill, pid, tid, signo); +} + +void SendSIGCONT(pid_t pid, pid_t tid) { + if (tid > 0) { + if (tgkill(pid, tid, ExceptionHandlerProtocol::kDumpDoneSignal) != 0) { + PLOG(ERROR) << "tgkill"; + } + return; + } + + std::vector threads; + if (!ReadThreadIDs(pid, &threads)) { + return; + } + for (const auto& thread : threads) { + if (tgkill(pid, thread, ExceptionHandlerProtocol::kDumpDoneSignal) != 0) { + PLOG(ERROR) << "tgkill"; + } + } +} + +bool SendCredentials(int client_sock) { + ExceptionHandlerProtocol::ServerToClientMessage message = {}; + message.type = + ExceptionHandlerProtocol::ServerToClientMessage::kTypeCredentials; + return UnixCredentialSocket::SendMsg( + client_sock, &message, sizeof(message)) == 0; +} + class PtraceStrategyDeciderImpl : public PtraceStrategyDecider { public: PtraceStrategyDeciderImpl() : PtraceStrategyDecider() {} ~PtraceStrategyDeciderImpl() = default; - Strategy ChooseStrategy(int sock, const ucred& client_credentials) override { + Strategy ChooseStrategy(int sock, + bool multiple_clients, + const ucred& client_credentials) override { + if (client_credentials.pid <= 0) { + LOG(ERROR) << "invalid credentials"; + return Strategy::kNoPtrace; + } + switch (GetPtraceScope()) { case PtraceScope::kClassic: - if (getuid() == client_credentials.uid) { + if (getuid() == client_credentials.uid || HaveCapSysPtrace()) { return Strategy::kDirectPtrace; } - return TryForkingBroker(sock); + return multiple_clients ? Strategy::kNoPtrace : TryForkingBroker(sock); case PtraceScope::kRestricted: + if (multiple_clients) { + return Strategy::kDirectPtrace; + } if (!SendMessageToClient(sock, - ServerToClientMessage::kTypeSetPtracer)) { + ExceptionHandlerProtocol:: + ServerToClientMessage::kTypeSetPtracer)) { return Strategy::kError; } - Errno status; + ExceptionHandlerProtocol::Errno status; if (!LoggingReadFileExactly(sock, &status, sizeof(status))) { return Strategy::kError; } @@ -169,12 +214,13 @@ class PtraceStrategyDeciderImpl : public PtraceStrategyDecider { private: static Strategy TryForkingBroker(int client_sock) { - if (!SendMessageToClient(client_sock, - ServerToClientMessage::kTypeForkBroker)) { + if (!SendMessageToClient( + client_sock, + ExceptionHandlerProtocol::ServerToClientMessage::kTypeForkBroker)) { return Strategy::kError; } - Errno status; + ExceptionHandlerProtocol::Errno status; if (!LoggingReadFileExactly(client_sock, &status, sizeof(status))) { return Strategy::kError; } @@ -190,12 +236,6 @@ class PtraceStrategyDeciderImpl : public PtraceStrategyDecider { } // namespace -struct ExceptionHandlerServer::Event { - enum class Type { kShutdown, kClientMessage } type; - - ScopedFileHandle fd; -}; - ExceptionHandlerServer::ExceptionHandlerServer() : clients_(), shutdown_event_(), @@ -211,7 +251,8 @@ void ExceptionHandlerServer::SetPtraceStrategyDecider( strategy_decider_ = std::move(decider); } -bool ExceptionHandlerServer::InitializeWithClient(ScopedFileHandle sock) { +bool ExceptionHandlerServer::InitializeWithClient(ScopedFileHandle sock, + bool multiple_clients) { INITIALIZATION_STATE_SET_INITIALIZING(initialized_); pollfd_.reset(epoll_create1(EPOLL_CLOEXEC)); @@ -239,7 +280,9 @@ bool ExceptionHandlerServer::InitializeWithClient(ScopedFileHandle sock) { return false; } - if (!InstallClientSocket(std::move(sock))) { + if (!InstallClientSocket(std::move(sock), + multiple_clients ? Event::Type::kSharedSocketMessage + : Event::Type::kClientMessage)) { return false; } @@ -281,8 +324,8 @@ void ExceptionHandlerServer::Stop() { } void ExceptionHandlerServer::HandleEvent(Event* event, uint32_t event_type) { - DCHECK_EQ(AsUnderlyingType(event->type), - AsUnderlyingType(Event::Type::kClientMessage)); + DCHECK_NE(AsUnderlyingType(event->type), + AsUnderlyingType(Event::Type::kShutdown)); if (event_type & EPOLLERR) { LogSocketError(event->fd.get()); @@ -306,16 +349,30 @@ void ExceptionHandlerServer::HandleEvent(Event* event, uint32_t event_type) { return; } -bool ExceptionHandlerServer::InstallClientSocket(ScopedFileHandle socket) { - int optval = 1; +bool ExceptionHandlerServer::InstallClientSocket(ScopedFileHandle socket, + Event::Type type) { + // The handler may not have permission to set SO_PASSCRED on the socket, but + // it doesn't need to if the client has already set it. + // https://bugs.chromium.org/p/crashpad/issues/detail?id=252 + int optval; socklen_t optlen = sizeof(optval); - if (setsockopt(socket.get(), SOL_SOCKET, SO_PASSCRED, &optval, optlen) != 0) { - PLOG(ERROR) << "setsockopt"; + if (getsockopt(socket.get(), SOL_SOCKET, SO_PASSCRED, &optval, &optlen) != + 0) { + PLOG(ERROR) << "getsockopt"; return false; } + if (!optval) { + optval = 1; + optlen = sizeof(optval); + if (setsockopt(socket.get(), SOL_SOCKET, SO_PASSCRED, &optval, optlen) != + 0) { + PLOG(ERROR) << "setsockopt"; + return false; + } + } auto event = std::make_unique(); - event->type = Event::Type::kClientMessage; + event->type = type; event->fd.reset(socket.release()); Event* eventp = event.get(); @@ -355,53 +412,24 @@ bool ExceptionHandlerServer::UninstallClientSocket(Event* event) { } bool ExceptionHandlerServer::ReceiveClientMessage(Event* event) { - ClientToServerMessage message; - iovec iov; - iov.iov_base = &message; - iov.iov_len = sizeof(message); - - msghdr msg; - msg.msg_name = nullptr; - msg.msg_namelen = 0; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - char cmsg_buf[CMSG_SPACE(sizeof(ucred))]; - msg.msg_control = cmsg_buf; - msg.msg_controllen = sizeof(cmsg_buf); - msg.msg_flags = 0; - - int res = HANDLE_EINTR(recvmsg(event->fd.get(), &msg, 0)); - if (res < 0) { - PLOG(ERROR) << "recvmsg"; - return false; - } - if (res == 0) { - // The client had an orderly shutdown. + ExceptionHandlerProtocol::ClientToServerMessage message; + ucred creds; + if (!UnixCredentialSocket::RecvMsg( + event->fd.get(), &message, sizeof(message), &creds)) { return false; } - if (msg.msg_name != nullptr || msg.msg_namelen != 0) { - LOG(ERROR) << "unexpected msg name"; - return false; - } + switch (message.type) { + case ExceptionHandlerProtocol::ClientToServerMessage::kTypeCheckCredentials: + return SendCredentials(event->fd.get()); - if (msg.msg_iovlen != 1) { - LOG(ERROR) << "unexpected iovlen"; - return false; - } - - if (msg.msg_iov[0].iov_len != sizeof(ClientToServerMessage)) { - LOG(ERROR) << "unexpected message size " << msg.msg_iov[0].iov_len; - return false; - } - auto client_msg = - reinterpret_cast(msg.msg_iov[0].iov_base); - - switch (client_msg->type) { - case ClientToServerMessage::kCrashDumpRequest: + case ExceptionHandlerProtocol::ClientToServerMessage::kTypeCrashDumpRequest: return HandleCrashDumpRequest( - msg, client_msg->client_info, event->fd.get()); + creds, + message.client_info, + message.requesting_thread_stack_address, + event->fd.get(), + event->type == Event::Type::kSharedSocketMessage); } DCHECK(false); @@ -410,53 +438,56 @@ bool ExceptionHandlerServer::ReceiveClientMessage(Event* event) { } bool ExceptionHandlerServer::HandleCrashDumpRequest( - const msghdr& msg, - const ClientInformation& client_info, - int client_sock) { - cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); - if (cmsg == nullptr) { - LOG(ERROR) << "missing credentials"; - return false; - } + const ucred& creds, + const ExceptionHandlerProtocol::ClientInformation& client_info, + VMAddress requesting_thread_stack_address, + int client_sock, + bool multiple_clients) { + pid_t client_process_id = creds.pid; + pid_t requesting_thread_id = -1; + uid_t client_uid = creds.uid; - if (cmsg->cmsg_level != SOL_SOCKET) { - LOG(ERROR) << "unexpected cmsg_level " << cmsg->cmsg_level; - return false; - } - - if (cmsg->cmsg_type != SCM_CREDENTIALS) { - LOG(ERROR) << "unexpected cmsg_type " << cmsg->cmsg_type; - return false; - } - - if (cmsg->cmsg_len != CMSG_LEN(sizeof(ucred))) { - LOG(ERROR) << "unexpected cmsg_len " << cmsg->cmsg_len; - return false; - } - - ucred* client_credentials = reinterpret_cast(CMSG_DATA(cmsg)); - pid_t client_process_id = client_credentials->pid; - - switch (strategy_decider_->ChooseStrategy(client_sock, *client_credentials)) { + switch ( + strategy_decider_->ChooseStrategy(client_sock, multiple_clients, creds)) { case PtraceStrategyDecider::Strategy::kError: + if (multiple_clients) { + SendSIGCONT(client_process_id, requesting_thread_id); + } return false; case PtraceStrategyDecider::Strategy::kNoPtrace: - return SendMessageToClient(client_sock, - ServerToClientMessage::kTypeCrashDumpFailed); + if (multiple_clients) { + SendSIGCONT(client_process_id, requesting_thread_id); + return true; + } + return SendMessageToClient( + client_sock, + ExceptionHandlerProtocol::ServerToClientMessage:: + kTypeCrashDumpFailed); - case PtraceStrategyDecider::Strategy::kDirectPtrace: - delegate_->HandleException(client_process_id, client_info); + case PtraceStrategyDecider::Strategy::kDirectPtrace: { + delegate_->HandleException(client_process_id, + client_uid, + client_info, + requesting_thread_stack_address, + &requesting_thread_id); + if (multiple_clients) { + SendSIGCONT(client_process_id, requesting_thread_id); + return true; + } break; + } case PtraceStrategyDecider::Strategy::kUseBroker: + DCHECK(!multiple_clients); delegate_->HandleExceptionWithBroker( - client_process_id, client_info, client_sock); + client_process_id, client_uid, client_info, client_sock); break; } - return SendMessageToClient(client_sock, - ServerToClientMessage::kTypeCrashDumpComplete); + return SendMessageToClient( + client_sock, + ExceptionHandlerProtocol::ServerToClientMessage::kTypeCrashDumpComplete); } } // namespace crashpad diff --git a/handler/linux/exception_handler_server.h b/handler/linux/exception_handler_server.h index 5f491826..ac430a4c 100644 --- a/handler/linux/exception_handler_server.h +++ b/handler/linux/exception_handler_server.h @@ -18,6 +18,7 @@ #include #include +#include #include #include @@ -26,6 +27,7 @@ #include "util/linux/exception_handler_protocol.h" #include "util/misc/address_types.h" #include "util/misc/initialization_state_dcheck.h" +#include "util/misc/uuid.h" namespace crashpad { @@ -53,9 +55,12 @@ class PtraceStrategyDecider { //! \brief Chooses an appropriate `ptrace` strategy. //! //! \param[in] sock A socket conncted to a ExceptionHandlerClient. + //! \param[in] multiple_clients `true` if the socket is connected to multiple + //! clients. The broker is not supported in this configuration. //! \param[in] client_credentials The credentials for the connected client. //! \return the chosen #Strategy. virtual Strategy ChooseStrategy(int sock, + bool multiple_clients, const ucred& client_credentials) = 0; protected: @@ -71,24 +76,43 @@ class ExceptionHandlerServer { //! \brief Called on receipt of a crash dump request from a client. //! //! \param[in] client_process_id The process ID of the crashing client. + //! \param[in] client_uid The user ID of the crashing client. //! \param[in] info Information on the client. + //! \param[in] requesting_thread_stack_address Any address within the stack + //! range for the the thread that sent the crash dump request. Optional. + //! If unspecified or 0, \a requesting_thread_id will be -1. + //! \param[out] requesting_thread_id The thread ID of the thread which + //! requested the crash dump if not `nullptr`. Set to -1 if the thread + //! ID could not be determined. Optional. + //! \param[out] local_report_id The unique identifier for the report created + //! in the local report database. Optional. //! \return `true` on success. `false` on failure with a message logged. - virtual bool HandleException(pid_t client_process_id, - const ClientInformation& info) = 0; + virtual bool HandleException( + pid_t client_process_id, + uid_t client_uid, + const ExceptionHandlerProtocol::ClientInformation& info, + VMAddress requesting_thread_stack_address = 0, + pid_t* requesting_thread_id = nullptr, + UUID* local_report_id = nullptr) = 0; //! \brief Called on the receipt of a crash dump request from a client for a //! crash that should be mediated by a PtraceBroker. //! //! \param[in] client_process_id The process ID of the crashing client. + //! \param[in] client_uid The uid of the crashing client. //! \param[in] info Information on the client. //! \param[in] broker_sock A socket connected to the PtraceBroker. + //! \param[out] local_report_id The unique identifier for the report created + //! in the local report database. Optional. //! \return `true` on success. `false` on failure with a message logged. - virtual bool HandleExceptionWithBroker(pid_t client_process_id, - const ClientInformation& info, - int broker_sock) = 0; + virtual bool HandleExceptionWithBroker( + pid_t client_process_id, + uid_t client_uid, + const ExceptionHandlerProtocol::ClientInformation& info, + int broker_sock, + UUID* local_report_id = nullptr) = 0; - protected: - ~Delegate() {} + virtual ~Delegate() {} }; ExceptionHandlerServer(); @@ -105,8 +129,11 @@ class ExceptionHandlerServer { //! This method must be successfully called before Run(). //! //! \param[in] sock A socket on which to receive client requests. + //! \param[in] multiple_clients `true` if this socket is used by multiple + //! clients. Using a broker process is not supported in this + //! configuration. //! \return `true` on success. `false` on failure with a message logged. - bool InitializeWithClient(ScopedFileHandle sock); + bool InitializeWithClient(ScopedFileHandle sock, bool multiple_clients); //! \brief Runs the exception-handling server. //! @@ -126,22 +153,39 @@ class ExceptionHandlerServer { void Stop(); private: - struct Event; + struct Event { + enum class Type { + // Used by Stop() to shutdown the server. + kShutdown, + + // A message from a client on a private socket connection. + kClientMessage, + + // A message from a client on a shared socket connection. + kSharedSocketMessage + }; + + Type type; + ScopedFileHandle fd; + }; void HandleEvent(Event* event, uint32_t event_type); - bool InstallClientSocket(ScopedFileHandle socket); + bool InstallClientSocket(ScopedFileHandle socket, Event::Type type); bool UninstallClientSocket(Event* event); bool ReceiveClientMessage(Event* event); - bool HandleCrashDumpRequest(const msghdr& msg, - const ClientInformation& client_info, - int client_sock); + bool HandleCrashDumpRequest( + const ucred& creds, + const ExceptionHandlerProtocol::ClientInformation& client_info, + VMAddress requesting_thread_stack_address, + int client_sock, + bool multiple_clients); std::unordered_map> clients_; std::unique_ptr shutdown_event_; std::unique_ptr strategy_decider_; Delegate* delegate_; ScopedFileHandle pollfd_; - bool keep_running_; + std::atomic keep_running_; InitializationStateDcheck initialized_; DISALLOW_COPY_AND_ASSIGN(ExceptionHandlerServer); diff --git a/handler/linux/exception_handler_server_test.cc b/handler/linux/exception_handler_server_test.cc index eda14d31..7da5babe 100644 --- a/handler/linux/exception_handler_server_test.cc +++ b/handler/linux/exception_handler_server_test.cc @@ -18,16 +18,23 @@ #include #include "base/logging.h" +#include "build/build_config.h" #include "gtest/gtest.h" +#include "snapshot/linux/process_snapshot_linux.h" #include "test/errors.h" #include "test/multiprocess.h" #include "util/linux/direct_ptrace_connection.h" #include "util/linux/exception_handler_client.h" #include "util/linux/ptrace_client.h" #include "util/linux/scoped_pr_set_ptracer.h" +#include "util/misc/uuid.h" #include "util/synchronization/semaphore.h" #include "util/thread/thread.h" +#if defined(OS_ANDROID) +#include +#endif + namespace crashpad { namespace test { namespace { @@ -101,7 +108,11 @@ class TestDelegate : public ExceptionHandlerServer::Delegate { } bool HandleException(pid_t client_process_id, - const ClientInformation& info) override { + uid_t client_uid, + const ExceptionHandlerProtocol::ClientInformation& info, + VMAddress requesting_thread_stack_address, + pid_t* requesting_thread_id = nullptr, + UUID* local_report_id = nullptr) override { DirectPtraceConnection connection; bool connected = connection.Initialize(client_process_id); EXPECT_TRUE(connected); @@ -109,12 +120,32 @@ class TestDelegate : public ExceptionHandlerServer::Delegate { last_exception_address_ = info.exception_information_address; last_client_ = client_process_id; sem_.Signal(); - return connected; + if (!connected) { + return false; + } + + if (requesting_thread_id) { + if (requesting_thread_stack_address) { + ProcessSnapshotLinux process_snapshot; + if (!process_snapshot.Initialize(&connection)) { + ADD_FAILURE(); + return false; + } + *requesting_thread_id = process_snapshot.FindThreadWithStackAddress( + requesting_thread_stack_address); + } else { + *requesting_thread_id = -1; + } + } + return true; } - bool HandleExceptionWithBroker(pid_t client_process_id, - const ClientInformation& info, - int broker_sock) override { + bool HandleExceptionWithBroker( + pid_t client_process_id, + uid_t client_uid, + const ExceptionHandlerProtocol::ClientInformation& info, + int broker_sock, + UUID* local_report_id = nullptr) override { PtraceClient client; bool connected = client.Initialize(broker_sock, client_process_id); EXPECT_TRUE(connected); @@ -140,12 +171,15 @@ class MockPtraceStrategyDecider : public PtraceStrategyDecider { ~MockPtraceStrategyDecider() {} - Strategy ChooseStrategy(int sock, const ucred& client_credentials) override { + Strategy ChooseStrategy(int sock, + bool multiple_clients, + const ucred& client_credentials) override { if (strategy_ == Strategy::kUseBroker) { - ServerToClientMessage message = {}; - message.type = ServerToClientMessage::kTypeForkBroker; + ExceptionHandlerProtocol::ServerToClientMessage message = {}; + message.type = + ExceptionHandlerProtocol::ServerToClientMessage::kTypeForkBroker; - Errno status; + ExceptionHandlerProtocol::Errno status; bool result = LoggingWriteFile(sock, &message, sizeof(message)) && LoggingReadFileExactly(sock, &status, sizeof(status)); EXPECT_TRUE(result); @@ -169,13 +203,14 @@ class MockPtraceStrategyDecider : public PtraceStrategyDecider { DISALLOW_COPY_AND_ASSIGN(MockPtraceStrategyDecider); }; -class ExceptionHandlerServerTest : public testing::Test { +class ExceptionHandlerServerTest : public testing::TestWithParam { public: ExceptionHandlerServerTest() : server_(), delegate_(), server_thread_(&server_, &delegate_), - sock_to_handler_() {} + sock_to_handler_(), + use_multi_client_socket_(GetParam()) {} ~ExceptionHandlerServerTest() = default; @@ -197,7 +232,7 @@ class ExceptionHandlerServerTest : public testing::Test { ~CrashDumpTest() = default; void MultiprocessParent() override { - ClientInformation info; + ExceptionHandlerProtocol::ClientInformation info; ASSERT_TRUE( LoggingReadFileExactly(ReadPipeHandle(), &info, sizeof(info))); @@ -216,9 +251,8 @@ class ExceptionHandlerServerTest : public testing::Test { void MultiprocessChild() override { ASSERT_EQ(close(server_test_->sock_to_client_), 0); - ClientInformation info; + ExceptionHandlerProtocol::ClientInformation info; info.exception_information_address = 42; - ASSERT_TRUE(LoggingWriteFile(WritePipeHandle(), &info, sizeof(info))); // If the current ptrace_scope is restricted, the broker needs to be set @@ -226,7 +260,8 @@ class ExceptionHandlerServerTest : public testing::Test { // ptracer allows the broker to inherit this condition. ScopedPrSetPtracer set_ptracer(getpid(), /* may_log= */ true); - ExceptionHandlerClient client(server_test_->SockToHandler()); + ExceptionHandlerClient client(server_test_->SockToHandler(), + server_test_->use_multi_client_socket_); ASSERT_EQ(client.RequestCrashDump(info), 0); } @@ -239,16 +274,18 @@ class ExceptionHandlerServerTest : public testing::Test { void ExpectCrashDumpUsingStrategy(PtraceStrategyDecider::Strategy strategy, bool succeeds) { - ScopedStopServerAndJoinThread stop_server(Server(), ServerThread()); - ServerThread()->Start(); - Server()->SetPtraceStrategyDecider( std::make_unique(strategy)); + ScopedStopServerAndJoinThread stop_server(Server(), ServerThread()); + ServerThread()->Start(); + CrashDumpTest test(this, succeeds); test.Run(); } + bool UsingMultiClientSocket() const { return use_multi_client_socket_; } + protected: void SetUp() override { int socks[2]; @@ -256,7 +293,8 @@ class ExceptionHandlerServerTest : public testing::Test { sock_to_handler_.reset(socks[0]); sock_to_client_ = socks[1]; - ASSERT_TRUE(server_.InitializeWithClient(ScopedFileHandle(socks[1]))); + ASSERT_TRUE(server_.InitializeWithClient(ScopedFileHandle(socks[1]), + use_multi_client_socket_)); } private: @@ -265,36 +303,37 @@ class ExceptionHandlerServerTest : public testing::Test { RunServerThread server_thread_; ScopedFileHandle sock_to_handler_; int sock_to_client_; + bool use_multi_client_socket_; DISALLOW_COPY_AND_ASSIGN(ExceptionHandlerServerTest); }; -TEST_F(ExceptionHandlerServerTest, ShutdownWithNoClients) { +TEST_P(ExceptionHandlerServerTest, ShutdownWithNoClients) { ServerThread()->Start(); Hangup(); ASSERT_TRUE(ServerThread()->JoinWithTimeout(5.0)); } -TEST_F(ExceptionHandlerServerTest, StopWithClients) { +TEST_P(ExceptionHandlerServerTest, StopWithClients) { ServerThread()->Start(); Server()->Stop(); ASSERT_TRUE(ServerThread()->JoinWithTimeout(5.0)); } -TEST_F(ExceptionHandlerServerTest, StopBeforeRun) { +TEST_P(ExceptionHandlerServerTest, StopBeforeRun) { Server()->Stop(); ServerThread()->Start(); ASSERT_TRUE(ServerThread()->JoinWithTimeout(5.0)); } -TEST_F(ExceptionHandlerServerTest, MultipleStops) { +TEST_P(ExceptionHandlerServerTest, MultipleStops) { ServerThread()->Start(); Server()->Stop(); Server()->Stop(); ASSERT_TRUE(ServerThread()->JoinWithTimeout(5.0)); } -TEST_F(ExceptionHandlerServerTest, RequestCrashDumpDefault) { +TEST_P(ExceptionHandlerServerTest, RequestCrashDumpDefault) { ScopedStopServerAndJoinThread stop_server(Server(), ServerThread()); ServerThread()->Start(); @@ -302,25 +341,35 @@ TEST_F(ExceptionHandlerServerTest, RequestCrashDumpDefault) { test.Run(); } -TEST_F(ExceptionHandlerServerTest, RequestCrashDumpNoPtrace) { +TEST_P(ExceptionHandlerServerTest, RequestCrashDumpNoPtrace) { ExpectCrashDumpUsingStrategy(PtraceStrategyDecider::Strategy::kNoPtrace, false); } -TEST_F(ExceptionHandlerServerTest, RequestCrashDumpForkBroker) { +TEST_P(ExceptionHandlerServerTest, RequestCrashDumpForkBroker) { + if (UsingMultiClientSocket()) { + // The broker is not supported with multiple clients connected on a single + // socket. + return; + } ExpectCrashDumpUsingStrategy(PtraceStrategyDecider::Strategy::kUseBroker, true); } -TEST_F(ExceptionHandlerServerTest, RequestCrashDumpDirectPtrace) { +TEST_P(ExceptionHandlerServerTest, RequestCrashDumpDirectPtrace) { ExpectCrashDumpUsingStrategy(PtraceStrategyDecider::Strategy::kDirectPtrace, true); } -TEST_F(ExceptionHandlerServerTest, RequestCrashDumpError) { +TEST_P(ExceptionHandlerServerTest, RequestCrashDumpError) { ExpectCrashDumpUsingStrategy(PtraceStrategyDecider::Strategy::kError, false); } +INSTANTIATE_TEST_SUITE_P(ExceptionHandlerServerTestSuite, + ExceptionHandlerServerTest, + testing::Bool() +); + } // namespace } // namespace test } // namespace crashpad diff --git a/handler/linux/handler_trampoline.cc b/handler/linux/handler_trampoline.cc new file mode 100644 index 00000000..34535631 --- /dev/null +++ b/handler/linux/handler_trampoline.cc @@ -0,0 +1,45 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +// The first argument passed to the trampoline is the name of the native library +// exporting the symbol `CrashpadHandlerMain`. The remaining arguments are the +// same as for `HandlerMain()`. +int main(int argc, char* argv[]) { + static constexpr char kTag[] = "crashpad"; + + if (argc < 2) { + __android_log_print(ANDROID_LOG_FATAL, kTag, "usage: %s ", argv[0]); + return EXIT_FAILURE; + } + + void* handle = dlopen(argv[1], RTLD_LAZY | RTLD_GLOBAL); + if (!handle) { + __android_log_print(ANDROID_LOG_FATAL, kTag, "dlopen: %s", dlerror()); + return EXIT_FAILURE; + } + + using MainType = int (*)(int, char*[]); + MainType crashpad_main = + reinterpret_cast(dlsym(handle, "CrashpadHandlerMain")); + if (!crashpad_main) { + __android_log_print(ANDROID_LOG_FATAL, kTag, "dlsym: %s", dlerror()); + return EXIT_FAILURE; + } + + return crashpad_main(argc - 1, argv + 1); +} diff --git a/handler/mac/crash_report_exception_handler.cc b/handler/mac/crash_report_exception_handler.cc index 9919e955..cccf1e94 100644 --- a/handler/mac/crash_report_exception_handler.cc +++ b/handler/mac/crash_report_exception_handler.cc @@ -28,6 +28,7 @@ #include "snapshot/crashpad_info_client_options.h" #include "snapshot/mac/process_snapshot_mac.h" #include "util/file/file_writer.h" +#include "util/mach/bootstrap.h" #include "util/mach/exc_client_variants.h" #include "util/mach/exception_behaviors.h" #include "util/mach/exception_types.h" diff --git a/handler/mac/crash_report_exception_handler.h b/handler/mac/crash_report_exception_handler.h index 0b44de67..b5a59e43 100644 --- a/handler/mac/crash_report_exception_handler.h +++ b/handler/mac/crash_report_exception_handler.h @@ -30,7 +30,8 @@ namespace crashpad { //! \brief An exception handler that writes crash reports for exception messages //! to a CrashReportDatabase. -class CrashReportExceptionHandler : public UniversalMachExcServer::Interface { +class CrashReportExceptionHandler final + : public UniversalMachExcServer::Interface { public: //! \brief Creates a new object that will store crash reports in \a database. //! diff --git a/handler/mac/file_limit_annotation.cc b/handler/mac/file_limit_annotation.cc index cf56eb86..a01e87d9 100644 --- a/handler/mac/file_limit_annotation.cc +++ b/handler/mac/file_limit_annotation.cc @@ -24,7 +24,7 @@ #include #include "base/format_macros.h" -#include "base/macros.h" +#include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "client/crashpad_info.h" #include "client/simple_string_dictionary.h" @@ -108,7 +108,7 @@ void RecordFileLimitAnnotation() { int mib[] = {CTL_KERN, KERN_MAXFILES}; size = sizeof(value); std::string max_files = FormatFromSysctl( - sysctl(mib, arraysize(mib), &value, &size, nullptr, 0), &value, &size); + sysctl(mib, base::size(mib), &value, &size, nullptr, 0), &value, &size); std::string open_files = CountOpenFileDescriptors(); diff --git a/handler/minidump_to_upload_parameters.cc b/handler/minidump_to_upload_parameters.cc index 03c6fe14..6b64081e 100644 --- a/handler/minidump_to_upload_parameters.cc +++ b/handler/minidump_to_upload_parameters.cc @@ -17,6 +17,7 @@ #include "base/logging.h" #include "client/annotation.h" #include "snapshot/module_snapshot.h" +#include "snapshot/process_snapshot.h" #include "util/stdlib/map_insert.h" namespace crashpad { @@ -49,7 +50,7 @@ std::map BreakpadHTTPFormParametersFromMinidump( } } - for (std::string annotation : module->AnnotationsVector()) { + for (const std::string& annotation : module->AnnotationsVector()) { list_annotations.append(annotation); list_annotations.append("\n"); } diff --git a/handler/minidump_to_upload_parameters.h b/handler/minidump_to_upload_parameters.h index 41056f70..94d396fa 100644 --- a/handler/minidump_to_upload_parameters.h +++ b/handler/minidump_to_upload_parameters.h @@ -18,10 +18,10 @@ #include #include -#include "snapshot/process_snapshot.h" - namespace crashpad { +class ProcessSnapshot; + //! \brief Given a ProcessSnapshot, returns a map of key-value pairs to use as //! HTTP form parameters for upload to a Breakpad crash report colleciton //! server. diff --git a/handler/win/.gitattributes b/handler/win/.gitattributes new file mode 100644 index 00000000..6495e512 --- /dev/null +++ b/handler/win/.gitattributes @@ -0,0 +1,21 @@ +# Copyright 2019 The Crashpad Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This should be a .cc file, which would allow its attributes to be controlled +# by the *.cc pattern in the root .gitattributes file, but it’s named with a +# .cpp extension instead. This file needs to be built with VC++6, a vintage 1998 +# compiler, which might not understand .cc to mean C++. Rather than setting +# attributes globally for .cpp files, which are undesirable (.cc should be used +# in its place), provide a file-specific mapping here. +/z7_test.cpp text eol=lf diff --git a/handler/win/crash_report_exception_handler.h b/handler/win/crash_report_exception_handler.h index c2781de3..566f0472 100644 --- a/handler/win/crash_report_exception_handler.h +++ b/handler/win/crash_report_exception_handler.h @@ -31,7 +31,8 @@ class CrashReportUploadThread; //! \brief An exception handler that writes crash reports for exception messages //! to a CrashReportDatabase. -class CrashReportExceptionHandler : public ExceptionHandlerServer::Delegate { +class CrashReportExceptionHandler final + : public ExceptionHandlerServer::Delegate { public: //! \brief Creates a new object that will store crash reports in \a database. //! diff --git a/handler/win/crashy_test_program.cc b/handler/win/crashy_test_program.cc index a4f4797b..9c8adbbd 100644 --- a/handler/win/crashy_test_program.cc +++ b/handler/win/crashy_test_program.cc @@ -26,7 +26,7 @@ #include "base/files/file_path.h" #include "base/logging.h" -#include "base/macros.h" +#include "base/stl_util.h" #include "build/build_config.h" #include "client/crashpad_client.h" #include "client/crashpad_info.h" @@ -83,11 +83,11 @@ void AllocateMemoryOfVariousProtections() { // All of these allocations are leaked, we want to view them in windbg via // !vprot. void* reserve = VirtualAlloc( - nullptr, arraysize(kPageTypes) * kPageSize, MEM_RESERVE, PAGE_READWRITE); + nullptr, base::size(kPageTypes) * kPageSize, MEM_RESERVE, PAGE_READWRITE); PCHECK(reserve) << "VirtualAlloc MEM_RESERVE"; uintptr_t reserve_as_int = reinterpret_cast(reserve); - for (size_t i = 0; i < arraysize(kPageTypes); ++i) { + for (size_t i = 0; i < base::size(kPageTypes); ++i) { void* result = VirtualAlloc(reinterpret_cast(reserve_as_int + (kPageSize * i)), kPageSize, @@ -101,7 +101,7 @@ DWORD WINAPI NullThreadProc(void* param) { return 0; } -// Creates a suspended background thread, and sets EDI/RDI to point at +// Creates a suspended background thread, and sets EDI/RDI/X17 to point at // g_test_memory so we can confirm it's available in the minidump. bool CreateThreadWithRegisterPointingToTestMemory() { HANDLE thread = CreateThread( @@ -121,6 +121,8 @@ bool CreateThreadWithRegisterPointingToTestMemory() { context.Rdi = reinterpret_cast(g_test_memory); #elif defined(ARCH_CPU_X86) context.Edi = reinterpret_cast(g_test_memory); +#elif defined(ARCH_CPU_ARM64) + context.X17 = reinterpret_cast(g_test_memory); #endif if (!SetThreadContext(thread, &context)) { PLOG(ERROR) << "SetThreadContext"; diff --git a/handler/win/hanging_program.cc b/handler/win/hanging_program.cc index 840c5125..f4e5b983 100644 --- a/handler/win/hanging_program.cc +++ b/handler/win/hanging_program.cc @@ -17,7 +17,7 @@ #include "base/debug/alias.h" #include "base/logging.h" -#include "base/macros.h" +#include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "client/crashpad_client.h" @@ -123,8 +123,8 @@ int wmain(int argc, wchar_t* argv[]) { fflush(stdout); // This is not expected to return. - DWORD count = - WaitForMultipleObjects(arraysize(threads), threads, true, INFINITE); + DWORD count = WaitForMultipleObjects( + static_cast(base::size(threads)), threads, true, INFINITE); if (count == WAIT_FAILED) { PLOG(ERROR) << "WaitForMultipleObjects"; } else { diff --git a/infra/config/PRESUBMIT.py b/infra/config/PRESUBMIT.py new file mode 100644 index 00000000..ca044332 --- /dev/null +++ b/infra/config/PRESUBMIT.py @@ -0,0 +1,23 @@ +# Copyright 2018 The Crashpad Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +def CheckChangeOnUpload(input_api, output_api): + return input_api.canned_checks.CheckChangedLUCIConfigs( + input_api, output_api) + + +def CheckChangeOnCommit(input_api, output_api): + return input_api.canned_checks.CheckChangedLUCIConfigs( + input_api, output_api) diff --git a/infra/config/cq.cfg b/infra/config/cq.cfg deleted file mode 100644 index 1fc33243..00000000 --- a/infra/config/cq.cfg +++ /dev/null @@ -1,61 +0,0 @@ -# Copyright 2017 The Crashpad Authors. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# See https://luci-config.appspot.com/schemas/projects/refs:cq.cfg for the -# documentation of this file format. - -version: 1 -cq_name: "crashpad" -cq_status_url: "https://chromium-cq-status.appspot.com" - -# This is required for gerrit projects. -git_repo_url: "https://chromium.googlesource.com/crashpad/crashpad" - -# Crashpad uses gerrit with no special options. -gerrit {} - -verifiers { - gerrit_cq_ability { - committer_list: "project-crashpad-tryjob-access" - dry_run_access_list: "project-crashpad-tryjob-access" - } - try_job { - buckets { - name: "master.client.crashpad" - builders { name: "crashpad_try_win_dbg" } - builders { name: "crashpad_try_win_rel" } - # https://crbug.com/743139 - disabled until we can move these to swarming, - # at which point we can just remove them. - #builders { name: "crashpad_try_win_x86_dbg" } - #builders { name: "crashpad_try_win_x86_rel" } - } - buckets { - name: "luci.crashpad.try" - builders { name: "crashpad_try_mac_dbg" } - builders { name: "crashpad_try_mac_rel" } - builders { name: "crashpad_try_win_dbg" experiment_percentage: 100 } - builders { name: "crashpad_try_win_rel" experiment_percentage: 100 } - builders { name: "crashpad_try_linux_dbg" } - builders { name: "crashpad_try_linux_rel" } - builders { name: "crashpad_try_fuchsia_arm64_dbg" } - builders { name: "crashpad_try_fuchsia_arm64_rel" } - builders { name: "crashpad_try_fuchsia_x64_dbg" } - builders { name: "crashpad_try_fuchsia_x64_rel" } - # https://crbug.com/743139 - disabled until we can move these to swarming, - # at which point we can just remove them. - #builders { name: "crashpad_try_win_x86_dbg" } - #builders { name: "crashpad_try_win_x86_rel" } - } - } -} diff --git a/minidump/BUILD.gn b/minidump/BUILD.gn index 88a10815..44425857 100644 --- a/minidump/BUILD.gn +++ b/minidump/BUILD.gn @@ -20,15 +20,12 @@ static_library("minidump") { "minidump_annotation_writer.h", "minidump_byte_array_writer.cc", "minidump_byte_array_writer.h", - "minidump_context.h", "minidump_context_writer.cc", "minidump_context_writer.h", "minidump_crashpad_info_writer.cc", "minidump_crashpad_info_writer.h", "minidump_exception_writer.cc", "minidump_exception_writer.h", - "minidump_extensions.cc", - "minidump_extensions.h", "minidump_file_writer.cc", "minidump_file_writer.h", "minidump_handle_writer.cc", @@ -72,6 +69,7 @@ static_library("minidump") { public_configs = [ "..:crashpad_config" ] public_deps = [ + ":format", "../compat", ] @@ -89,6 +87,25 @@ static_library("minidump") { } } +# :format is the only part of minidump that snapshot may depend on. +static_library("format") { + sources = [ + "minidump_context.h", + "minidump_extensions.cc", + "minidump_extensions.h", + ] + + public_configs = [ "..:crashpad_config" ] + + public_deps = [ "../compat" ] + + deps = [ + "../snapshot:context", + "../third_party/mini_chromium:base", + "../util", + ] +} + static_library("test_support") { testonly = true @@ -113,13 +130,14 @@ static_library("test_support") { public_configs = [ "..:crashpad_config" ] - public_deps = [ - ":minidump", - ] + public_deps = [ ":minidump" ] deps = [ + "../snapshot:test_support", + "../test", "../third_party/gtest:gtest", "../third_party/mini_chromium:base", + "../util", ] if (crashpad_is_win) { @@ -154,6 +172,8 @@ source_set("minidump_test") { "minidump_writable_test.cc", ] + configs += [ "../build:crashpad_is_in_fuchsia" ] + deps = [ ":test_support", "../snapshot:test_support", @@ -166,4 +186,6 @@ source_set("minidump_test") { if (crashpad_is_win) { cflags = [ "/wd4201" ] # nonstandard extension used : nameless struct/union } + + configs += [ "..:disable_ubsan" ] } diff --git a/minidump/minidump_annotation_writer_test.cc b/minidump/minidump_annotation_writer_test.cc index 1641d1d6..dc05cc64 100644 --- a/minidump/minidump_annotation_writer_test.cc +++ b/minidump/minidump_annotation_writer_test.cc @@ -16,7 +16,7 @@ #include -#include "base/macros.h" +#include "base/stl_util.h" #include "gtest/gtest.h" #include "minidump/minidump_extensions.h" #include "minidump/test/minidump_byte_array_writer_test_util.h" @@ -107,7 +107,7 @@ TEST(MinidumpAnnotationWriter, ThreeItems) { MinidumpAnnotationListWriter list_writer; - for (size_t i = 0; i < arraysize(kNames); ++i) { + for (size_t i = 0; i < base::size(kNames); ++i) { auto annotation = std::make_unique(); annotation->InitializeWithData(kNames[i], kTypes[i], kValues[i]); list_writer.AddObject(std::move(annotation)); diff --git a/minidump/minidump_byte_array_writer_test.cc b/minidump/minidump_byte_array_writer_test.cc index f20ad35a..e4cd526e 100644 --- a/minidump/minidump_byte_array_writer_test.cc +++ b/minidump/minidump_byte_array_writer_test.cc @@ -17,6 +17,7 @@ #include #include "base/format_macros.h" +#include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "gtest/gtest.h" #include "minidump/test/minidump_writable_test_util.h" @@ -34,7 +35,7 @@ TEST(MinidumpByteArrayWriter, Write) { {}, }; - for (size_t i = 0; i < arraysize(kTests); ++i) { + for (size_t i = 0; i < base::size(kTests); ++i) { SCOPED_TRACE(base::StringPrintf("index %" PRIuS, i)); StringFile string_file; @@ -66,7 +67,7 @@ TEST(MinidumpByteArrayWriter, SetData) { {}, }; - for (size_t i = 0; i < arraysize(kTests); ++i) { + for (size_t i = 0; i < base::size(kTests); ++i) { SCOPED_TRACE(base::StringPrintf("index %" PRIuS, i)); crashpad::MinidumpByteArrayWriter writer; diff --git a/minidump/minidump_context.h b/minidump/minidump_context.h index 6f51b5a4..3a3e603c 100644 --- a/minidump/minidump_context.h +++ b/minidump/minidump_context.h @@ -389,11 +389,16 @@ struct MinidumpContextARM { //! \brief 64-bit ARM-specifc flags for MinidumpContextARM64::context_flags. enum MinidumpContextARM64Flags : uint32_t { //! \brief Identifies the context structure as 64-bit ARM. - kMinidumpContextARM64 = 0x80000000, + kMinidumpContextARM64 = 0x00400000, + + //! \brief Indicates the validity of control registers. + //! + //! Registers `fp`, `lr`, `sp`, `pc`, and `cpsr`. + kMinidumpContextARM64Control = kMinidumpContextARM64 | 0x00000001, //! \brief Indicates the validty of integer registers. //! - //! Registers `x0`-`x31`, `pc`, and `cpsr`. + //! Registers `x0`-`x28`. kMinidumpContextARM64Integer = kMinidumpContextARM64 | 0x00000002, //! \brief Indicates the validity of fpsimd registers. @@ -401,17 +406,37 @@ enum MinidumpContextARM64Flags : uint32_t { //! Registers `v0`-`v31`, `fpsr`, and `fpcr` are valid. kMinidumpContextARM64Fpsimd = kMinidumpContextARM64 | 0x00000004, + //! \brief Indicates the validity of debug registers. + //! + //! `bcr`, `bvr`, `wcr`, and `wvr` are valid. + kMinidumpContextARM64Debug = kMinidumpContextARM64 | 0x00000008, + + //! \brief Indicates the validity of control, integer and floating point + //! registers. + kMinidumpContextARM64Full = kMinidumpContextARM64Control | + kMinidumpContextARM64Integer | + kMinidumpContextARM64Fpsimd, + //! \brief Indicates the validity of all registers. kMinidumpContextARM64All = - kMinidumpContextARM64Integer | kMinidumpContextARM64Fpsimd, + kMinidumpContextARM64Full | kMinidumpContextARM64Debug, }; //! \brief A 64-bit ARM CPU context (register state) carried in a minidump file. struct MinidumpContextARM64 { - uint64_t context_flags; + uint32_t context_flags; - //! \brief General-purpose registers `x0`-`x30`. - uint64_t regs[31]; + //! \brief Current program status register. + uint32_t cpsr; + + //! \brief General-purpose registers `x0`-`x28`. + uint64_t regs[29]; + + //! \brief Frame pointer or `x29`. + uint64_t fp; + + //! \brief Link register or `x30`. + uint64_t lr; //! \brief Stack pointer or `x31`. uint64_t sp; @@ -419,17 +444,20 @@ struct MinidumpContextARM64 { //! \brief Program counter. uint64_t pc; - //! \brief Current program status register. - uint32_t cpsr; - - //! \brief Floating-point status register. - uint32_t fpsr; + //! \brief NEON registers `v0`-`v31`. + uint128_struct fpsimd[32]; //! \brief Floating-point control register. uint32_t fpcr; - //! \brief NEON registers `v0`-`v31`. - uint128_struct fpsimd[32]; + //! \brief Floating-point status register. + uint32_t fpsr; + + //! \brief Debug registers. + uint32_t bcr[8]; + uint64_t bvr[8]; + uint32_t wcr[2]; + uint64_t wvr[2]; }; //! \brief 32bit MIPS-specifc flags for MinidumpContextMIPS::context_flags. diff --git a/minidump/minidump_context_writer.cc b/minidump/minidump_context_writer.cc index 20adbf3f..d7e53a49 100644 --- a/minidump/minidump_context_writer.cc +++ b/minidump/minidump_context_writer.cc @@ -321,26 +321,29 @@ void MinidumpContextARM64Writer::InitializeFromSnapshot( DCHECK_EQ(state(), kStateMutable); DCHECK_EQ(context_.context_flags, kMinidumpContextARM64); - context_.context_flags = kMinidumpContextARM64All; + context_.context_flags = kMinidumpContextARM64Full; - static_assert(sizeof(context_.regs) == sizeof(context_snapshot->regs), - "GPRs size mismatch"); + static_assert( + sizeof(context_.regs) == sizeof(context_snapshot->regs) - + 2 * sizeof(context_snapshot->regs[0]), + "GPRs size mismatch"); memcpy(context_.regs, context_snapshot->regs, sizeof(context_.regs)); + context_.fp = context_snapshot->regs[29]; + context_.lr = context_snapshot->regs[30]; context_.sp = context_snapshot->sp; context_.pc = context_snapshot->pc; + context_.cpsr = context_snapshot->spsr; - if (context_snapshot->pstate > - std::numeric_limits::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)); + context_.fpcr = context_snapshot->fpcr; + context_.fpsr = context_snapshot->fpsr; + + memset(context_.bcr, 0, sizeof(context_.bcr)); + memset(context_.bvr, 0, sizeof(context_.bvr)); + memset(context_.wcr, 0, sizeof(context_.wcr)); + memset(context_.wvr, 0, sizeof(context_.wvr)); } bool MinidumpContextARM64Writer::WriteObject(FileWriterInterface* file_writer) { diff --git a/minidump/minidump_exception_writer.cc b/minidump/minidump_exception_writer.cc index d870de36..b0e1e627 100644 --- a/minidump/minidump_exception_writer.cc +++ b/minidump/minidump_exception_writer.cc @@ -21,7 +21,7 @@ #include "minidump/minidump_context_writer.h" #include "snapshot/exception_snapshot.h" #include "util/file/file_writer.h" -#include "util/misc/arraysize_unsafe.h" +#include "util/misc/arraysize.h" namespace crashpad { @@ -65,7 +65,7 @@ void MinidumpExceptionWriter::SetExceptionInformation( const size_t parameters = exception_information.size(); constexpr size_t kMaxParameters = - ARRAYSIZE_UNSAFE(exception_.ExceptionRecord.ExceptionInformation); + ArraySize(exception_.ExceptionRecord.ExceptionInformation); CHECK_LE(parameters, kMaxParameters); exception_.ExceptionRecord.NumberParameters = diff --git a/minidump/minidump_exception_writer_test.cc b/minidump/minidump_exception_writer_test.cc index e4dc5faa..75adbee2 100644 --- a/minidump/minidump_exception_writer_test.cc +++ b/minidump/minidump_exception_writer_test.cc @@ -17,6 +17,7 @@ #include #include +#include "base/stl_util.h" #include "gtest/gtest.h" #include "minidump/minidump_context.h" #include "minidump/minidump_context_writer.h" @@ -80,7 +81,7 @@ void ExpectExceptionStream(const MINIDUMP_EXCEPTION_STREAM* expected, expected->ExceptionRecord.NumberParameters); EXPECT_EQ(observed->ExceptionRecord.__unusedAlignment, 0u); for (size_t index = 0; - index < arraysize(observed->ExceptionRecord.ExceptionInformation); + index < base::size(observed->ExceptionRecord.ExceptionInformation); ++index) { EXPECT_EQ(observed->ExceptionRecord.ExceptionInformation[index], expected->ExceptionRecord.ExceptionInformation[index]); diff --git a/minidump/minidump_extensions.h b/minidump/minidump_extensions.h index f3701dc2..97276d52 100644 --- a/minidump/minidump_extensions.h +++ b/minidump/minidump_extensions.h @@ -25,14 +25,15 @@ #include "util/misc/pdb_structures.h" #include "util/misc/uuid.h" +#if defined(COMPILER_MSVC) // C4200 is "nonstandard extension used : zero-sized array in struct/union". // We would like to globally disable this warning, but unfortunately, the // compiler is buggy and only supports disabling it with a pragma, so we can't -// disable it with other silly warnings in build/common.gypi. See: +// disable it with other silly warnings in the build files. See: // https://connect.microsoft.com/VisualStudio/feedback/details/1114440 -MSVC_PUSH_DISABLE_WARNING(4200); +#pragma warning(push) +#pragma warning(disable: 4200) -#if defined(COMPILER_MSVC) #define PACKED #pragma pack(push, 1) #else @@ -92,10 +93,18 @@ enum MinidumpStreamType : uint32_t { //! \sa MemoryInfoListStream kMinidumpStreamTypeMemoryInfoList = MemoryInfoListStream, + //! \brief The last reserved minidump stream. + //! + //! \sa MemoryInfoListStream + kMinidumpStreamTypeLastReservedStream = LastReservedStream, + // 0x4350 = "CP" //! \brief The stream type for MinidumpCrashpadInfo. kMinidumpStreamTypeCrashpadInfo = 0x43500001, + + //! \brief The last reserved crashpad stream. + kMinidumpStreamTypeCrashpadLastReservedStream = 0x4350ffff, }; //! \brief A variable-length UTF-8-encoded string carried within a minidump @@ -233,7 +242,7 @@ enum MinidumpOS : uint32_t { kMinidumpOSMacOSX = 0x8101, //! \brief iOS, Darwin for mobile devices. - kMinidumpOSiOS = 0x8102, + kMinidumpOSIOS = 0x8102, //! \brief Linux, not including Android. kMinidumpOSLinux = 0x8201, @@ -255,7 +264,6 @@ enum MinidumpOS : uint32_t { kMinidumpOSUnknown = 0xffffffff, }; - //! \brief A list of ::RVA pointers. struct ALIGNAS(4) PACKED MinidumpRVAList { //! \brief The number of children present in the #children array. @@ -490,11 +498,10 @@ struct ALIGNAS(4) PACKED MinidumpCrashpadInfo { #if defined(COMPILER_MSVC) #pragma pack(pop) +#pragma warning(pop) // C4200 #endif // COMPILER_MSVC #undef PACKED -MSVC_POP_WARNING(); // C4200 - } // namespace crashpad #endif // CRASHPAD_MINIDUMP_MINIDUMP_EXTENSIONS_H_ diff --git a/minidump/minidump_file_writer.cc b/minidump/minidump_file_writer.cc index 72265453..6727a0dc 100644 --- a/minidump/minidump_file_writer.cc +++ b/minidump/minidump_file_writer.cc @@ -211,17 +211,30 @@ bool MinidumpFileWriter::AddUserExtensionStream( } bool MinidumpFileWriter::WriteEverything(FileWriterInterface* file_writer) { + return WriteMinidump(file_writer, true); +} + +bool MinidumpFileWriter::WriteMinidump(FileWriterInterface* file_writer, + bool allow_seek) { DCHECK_EQ(state(), kStateMutable); - FileOffset start_offset = file_writer->Seek(0, SEEK_CUR); - if (start_offset < 0) { - return false; + FileOffset start_offset = -1; + if (allow_seek) { + start_offset = file_writer->Seek(0, SEEK_CUR); + if (start_offset < 0) { + return false; + } + } else { + header_.Signature = MINIDUMP_SIGNATURE; } if (!MinidumpWritable::WriteEverything(file_writer)) { return false; } + if (!allow_seek) + return true; + FileOffset end_offset = file_writer->Seek(0, SEEK_CUR); if (end_offset < 0) { return false; @@ -232,7 +245,7 @@ bool MinidumpFileWriter::WriteEverything(FileWriterInterface* file_writer) { // it as a valid minidump file. header_.Signature = MINIDUMP_SIGNATURE; - if (file_writer->Seek(start_offset, SEEK_SET) != 0) { + if (file_writer->Seek(start_offset, SEEK_SET) < 0) { return false; } diff --git a/minidump/minidump_file_writer.h b/minidump/minidump_file_writer.h index ce2f1d75..0a1b064f 100644 --- a/minidump/minidump_file_writer.h +++ b/minidump/minidump_file_writer.h @@ -134,6 +134,21 @@ class MinidumpFileWriter final : public internal::MinidumpWritable { //! mistaken for valid ones. bool WriteEverything(FileWriterInterface* file_writer) override; + //! \brief Writes this object to a minidump file. + //! + //! Same as \a WriteEverything, but give the option to disable the seek. It + //! is typically used to write to stream backed \a FileWriterInterface which + //! doesn't support seek. + //! + //! \param[in] file_writer The file writer to receive the minidump file’s + //! content. + //! + //! \param[in] allow_seek Whether seek is allowed. + //! + //! \return `true` on success. `false` on failure, with an appropriate message + //! logged. + bool WriteMinidump(FileWriterInterface* file_writer, bool allow_seek); + protected: // MinidumpWritable: bool Freeze() override; diff --git a/minidump/minidump_file_writer_test.cc b/minidump/minidump_file_writer_test.cc index 730da26b..19eaae41 100644 --- a/minidump/minidump_file_writer_test.cc +++ b/minidump/minidump_file_writer_test.cc @@ -20,7 +20,8 @@ #include #include -#include "base/compiler_specific.h" +#include "base/stl_util.h" +#include "build/build_config.h" #include "gtest/gtest.h" #include "minidump/minidump_stream_writer.h" #include "minidump/minidump_user_extension_stream_data_source.h" @@ -35,7 +36,9 @@ #include "snapshot/test/test_system_snapshot.h" #include "snapshot/test/test_thread_snapshot.h" #include "test/gtest_death.h" +#include "util/file/output_stream_file_writer.h" #include "util/file/string_file.h" +#include "util/stream/output_stream_interface.h" namespace crashpad { namespace test { @@ -87,6 +90,22 @@ class TestStream final : public internal::MinidumpStreamWriter { DISALLOW_COPY_AND_ASSIGN(TestStream); }; +class StringFileOutputStream : public OutputStreamInterface { + public: + StringFileOutputStream() = default; + ~StringFileOutputStream() override = default; + bool Write(const uint8_t* data, size_t size) override { + return string_file_.Write(data, size); + } + bool Flush() override { return true; } + const StringFile& string_file() const { return string_file_; } + + private: + StringFile string_file_; + + DISALLOW_COPY_AND_ASSIGN(StringFileOutputStream); +}; + TEST(MinidumpFileWriter, OneStream) { MinidumpFileWriter minidump_file; constexpr time_t kTimestamp = 0x155d2fb8; @@ -134,7 +153,7 @@ TEST(MinidumpFileWriter, AddUserExtensionStream) { minidump_file.SetTimestamp(kTimestamp); static constexpr uint8_t kStreamData[] = "Hello World!"; - constexpr size_t kStreamSize = arraysize(kStreamData); + constexpr size_t kStreamSize = base::size(kStreamData); constexpr MinidumpStreamType kStreamType = static_cast(0x4d); @@ -392,21 +411,16 @@ TEST(MinidumpFileWriter, InitializeFromSnapshot_Basic) { } TEST(MinidumpFileWriter, InitializeFromSnapshot_Exception) { - // In a 32-bit environment, this will give a “timestamp out of range” warning, + // In a 32-bit environment, this will give a “timestamp out of range” warning, // but the test should complete without failure. constexpr uint32_t kSnapshotTime = 0xfd469ab8; -#if defined(__clang__) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wconstant-conversion" -#define DISABLED_WCONSTANT_CONVERSION -#endif // __clang__ - MSVC_SUPPRESS_WARNING(4309); // Truncation of constant value. - MSVC_SUPPRESS_WARNING(4838); // Narrowing conversion. - constexpr timeval kSnapshotTimeval = {static_cast(kSnapshotTime), 0}; -#if defined(DISABLED_WCONSTANT_CONVERSION) -#pragma clang diagnostic pop -#undef DISABLED_WCONSTANT_CONVERSION -#endif // DISABLED_WCONSTANT_CONVERSION + constexpr timeval kSnapshotTimeval = { +#ifdef OS_WIN + static_cast(kSnapshotTime), +#else + static_cast(kSnapshotTime), +#endif + 0}; TestProcessSnapshot process_snapshot; process_snapshot.SetSnapshotTime(kSnapshotTimeval); @@ -582,6 +596,51 @@ TEST(MinidumpFileWriter, SameStreamType) { EXPECT_EQ(memcmp(stream_data, expected_stream.c_str(), kStream0Size), 0); } +TEST(MinidumpFileWriter, WriteMinidumpDisallowSeek) { + MinidumpFileWriter minidump_file; + constexpr time_t kTimestamp = 0x155d2fb8; + minidump_file.SetTimestamp(kTimestamp); + + constexpr size_t kStreamSize = 5; + constexpr MinidumpStreamType kStreamType = + static_cast(0x4d); + constexpr uint8_t kStreamValue = 0x5a; + auto stream = + std::make_unique(kStreamType, kStreamSize, kStreamValue); + ASSERT_TRUE(minidump_file.AddStream(std::move(stream))); + + std::unique_ptr string_file_output_stream = + std::make_unique(); + const StringFile& string_file = string_file_output_stream->string_file(); + OutputStreamFileWriter output_stream(std::move(string_file_output_stream)); + ASSERT_TRUE(minidump_file.WriteMinidump(&output_stream, false)); + ASSERT_TRUE(output_stream.Flush()); + + constexpr size_t kDirectoryOffset = sizeof(MINIDUMP_HEADER); + constexpr size_t kStreamOffset = + kDirectoryOffset + sizeof(MINIDUMP_DIRECTORY); + constexpr size_t kFileSize = kStreamOffset + kStreamSize; + + ASSERT_EQ(string_file.string().size(), kFileSize); + + const MINIDUMP_DIRECTORY* directory; + const MINIDUMP_HEADER* header = + MinidumpHeaderAtStart(string_file.string(), &directory); + ASSERT_NO_FATAL_FAILURE(VerifyMinidumpHeader(header, 1, kTimestamp)); + ASSERT_TRUE(directory); + + EXPECT_EQ(directory[0].StreamType, kStreamType); + EXPECT_EQ(directory[0].Location.DataSize, kStreamSize); + EXPECT_EQ(directory[0].Location.Rva, kStreamOffset); + + const uint8_t* stream_data = MinidumpWritableAtLocationDescriptor( + string_file.string(), directory[0].Location); + ASSERT_TRUE(stream_data); + + std::string expected_stream(kStreamSize, kStreamValue); + EXPECT_EQ(memcmp(stream_data, expected_stream.c_str(), kStreamSize), 0); +} + } // namespace } // namespace test } // namespace crashpad diff --git a/minidump/minidump_memory_writer_test.cc b/minidump/minidump_memory_writer_test.cc index 60b7fa8a..90287cba 100644 --- a/minidump/minidump_memory_writer_test.cc +++ b/minidump/minidump_memory_writer_test.cc @@ -17,6 +17,7 @@ #include #include "base/format_macros.h" +#include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "gtest/gtest.h" #include "minidump/minidump_extensions.h" @@ -340,7 +341,7 @@ TEST(MinidumpMemoryWriter, ExtraMemory) { TEST(MinidumpMemoryWriter, AddFromSnapshot) { MINIDUMP_MEMORY_DESCRIPTOR expect_memory_descriptors[3] = {}; - uint8_t values[arraysize(expect_memory_descriptors)] = {}; + uint8_t values[base::size(expect_memory_descriptors)] = {}; expect_memory_descriptors[0].StartOfMemoryRange = 0; expect_memory_descriptors[0].Memory.DataSize = 0x1000; @@ -356,8 +357,7 @@ TEST(MinidumpMemoryWriter, AddFromSnapshot) { std::vector> memory_snapshots_owner; std::vector memory_snapshots; - for (size_t index = 0; - index < arraysize(expect_memory_descriptors); + for (size_t index = 0; index < base::size(expect_memory_descriptors); ++index) { memory_snapshots_owner.push_back(std::make_unique()); TestMemorySnapshot* memory_snapshot = memory_snapshots_owner.back().get(); @@ -396,7 +396,7 @@ TEST(MinidumpMemoryWriter, AddFromSnapshot) { TEST(MinidumpMemoryWriter, CoalesceExplicitMultiple) { MINIDUMP_MEMORY_DESCRIPTOR expect_memory_descriptors[4] = {}; - uint8_t values[arraysize(expect_memory_descriptors)] = {}; + uint8_t values[base::size(expect_memory_descriptors)] = {}; expect_memory_descriptors[0].StartOfMemoryRange = 0; expect_memory_descriptors[0].Memory.DataSize = 1000; diff --git a/minidump/minidump_misc_info_writer.cc b/minidump/minidump_misc_info_writer.cc index d83ed237..a1340760 100644 --- a/minidump/minidump_misc_info_writer.cc +++ b/minidump/minidump_misc_info_writer.cc @@ -18,6 +18,7 @@ #include "base/logging.h" #include "base/numerics/safe_conversions.h" +#include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "build/build_config.h" @@ -26,7 +27,6 @@ #include "snapshot/process_snapshot.h" #include "snapshot/system_snapshot.h" #include "util/file/file_writer.h" -#include "util/misc/arraysize_unsafe.h" #include "util/numeric/in_range_cast.h" #include "util/numeric/safe_assignment.h" @@ -302,7 +302,7 @@ void MinidumpMiscInfoWriter::SetTimeZone(uint32_t time_zone_id, internal::MinidumpWriterUtil::AssignUTF8ToUTF16( misc_info_.TimeZone.StandardName, - ARRAYSIZE_UNSAFE(misc_info_.TimeZone.StandardName), + base::size(misc_info_.TimeZone.StandardName), standard_name); misc_info_.TimeZone.StandardDate = standard_date; @@ -310,7 +310,7 @@ void MinidumpMiscInfoWriter::SetTimeZone(uint32_t time_zone_id, internal::MinidumpWriterUtil::AssignUTF8ToUTF16( misc_info_.TimeZone.DaylightName, - ARRAYSIZE_UNSAFE(misc_info_.TimeZone.DaylightName), + base::size(misc_info_.TimeZone.DaylightName), daylight_name); misc_info_.TimeZone.DaylightDate = daylight_date; @@ -327,12 +327,10 @@ void MinidumpMiscInfoWriter::SetBuildString( misc_info_.Flags1 |= MINIDUMP_MISC4_BUILDSTRING; internal::MinidumpWriterUtil::AssignUTF8ToUTF16( - misc_info_.BuildString, - ARRAYSIZE_UNSAFE(misc_info_.BuildString), - build_string); + misc_info_.BuildString, base::size(misc_info_.BuildString), build_string); internal::MinidumpWriterUtil::AssignUTF8ToUTF16( misc_info_.DbgBldStr, - ARRAYSIZE_UNSAFE(misc_info_.DbgBldStr), + base::size(misc_info_.DbgBldStr), debug_build_string); } diff --git a/minidump/minidump_misc_info_writer_test.cc b/minidump/minidump_misc_info_writer_test.cc index 3c60ebcc..bd927338 100644 --- a/minidump/minidump_misc_info_writer_test.cc +++ b/minidump/minidump_misc_info_writer_test.cc @@ -21,6 +21,7 @@ #include "base/compiler_specific.h" #include "base/format_macros.h" +#include "base/stl_util.h" #include "base/strings/string16.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" @@ -31,7 +32,6 @@ #include "snapshot/test/test_process_snapshot.h" #include "snapshot/test/test_system_snapshot.h" #include "util/file/string_file.h" -#include "util/misc/arraysize_unsafe.h" #include "util/stdlib/strlcpy.h" namespace crashpad { @@ -127,7 +127,7 @@ void ExpectMiscInfoEqual( SCOPED_TRACE("Standard"); ExpectNULPaddedString16Equal(expected->TimeZone.StandardName, observed->TimeZone.StandardName, - arraysize(expected->TimeZone.StandardName)); + base::size(expected->TimeZone.StandardName)); ExpectSystemTimeEqual(&expected->TimeZone.StandardDate, &observed->TimeZone.StandardDate); EXPECT_EQ(observed->TimeZone.StandardBias, expected->TimeZone.StandardBias); @@ -136,7 +136,7 @@ void ExpectMiscInfoEqual( SCOPED_TRACE("Daylight"); ExpectNULPaddedString16Equal(expected->TimeZone.DaylightName, observed->TimeZone.DaylightName, - arraysize(expected->TimeZone.DaylightName)); + base::size(expected->TimeZone.DaylightName)); ExpectSystemTimeEqual(&expected->TimeZone.DaylightDate, &observed->TimeZone.DaylightDate); EXPECT_EQ(observed->TimeZone.DaylightBias, expected->TimeZone.DaylightBias); @@ -154,13 +154,13 @@ void ExpectMiscInfoEqual( SCOPED_TRACE("BuildString"); ExpectNULPaddedString16Equal(expected->BuildString, observed->BuildString, - arraysize(expected->BuildString)); + base::size(expected->BuildString)); } { SCOPED_TRACE("DbgBldStr"); ExpectNULPaddedString16Equal(expected->DbgBldStr, observed->DbgBldStr, - arraysize(expected->DbgBldStr)); + base::size(expected->DbgBldStr)); } } @@ -176,7 +176,7 @@ void ExpectMiscInfoEqual( EXPECT_EQ(observed->XStateData.EnabledFeatures, expected->XStateData.EnabledFeatures); for (size_t feature_index = 0; - feature_index < arraysize(observed->XStateData.Features); + feature_index < base::size(observed->XStateData.Features); ++feature_index) { SCOPED_TRACE(base::StringPrintf("feature_index %" PRIuS, feature_index)); EXPECT_EQ(observed->XStateData.Features[feature_index].Offset, @@ -396,7 +396,7 @@ TEST(MinidumpMiscInfoWriter, TimeZone) { base::string16 standard_name_utf16 = base::UTF8ToUTF16(kStandardName); c16lcpy(expected.TimeZone.StandardName, standard_name_utf16.c_str(), - ARRAYSIZE_UNSAFE(expected.TimeZone.StandardName)); + base::size(expected.TimeZone.StandardName)); memcpy(&expected.TimeZone.StandardDate, &kStandardDate, sizeof(expected.TimeZone.StandardDate)); @@ -404,7 +404,7 @@ TEST(MinidumpMiscInfoWriter, TimeZone) { base::string16 daylight_name_utf16 = base::UTF8ToUTF16(kDaylightName); c16lcpy(expected.TimeZone.DaylightName, daylight_name_utf16.c_str(), - ARRAYSIZE_UNSAFE(expected.TimeZone.DaylightName)); + base::size(expected.TimeZone.DaylightName)); memcpy(&expected.TimeZone.DaylightDate, &kDaylightDate, sizeof(expected.TimeZone.DaylightDate)); @@ -424,10 +424,9 @@ TEST(MinidumpMiscInfoWriter, TimeZoneStringsOverflow) { constexpr int32_t kBias = 300; MINIDUMP_MISC_INFO_N tmp; ALLOW_UNUSED_LOCAL(tmp); - std::string standard_name(ARRAYSIZE_UNSAFE(tmp.TimeZone.StandardName) + 1, - 's'); + std::string standard_name(base::size(tmp.TimeZone.StandardName) + 1, 's'); constexpr int32_t kStandardBias = 0; - std::string daylight_name(ARRAYSIZE_UNSAFE(tmp.TimeZone.DaylightName), 'd'); + std::string daylight_name(base::size(tmp.TimeZone.DaylightName), 'd'); constexpr int32_t kDaylightBias = -60; // Test using kSystemTimeZero, because not all platforms will be able to @@ -458,7 +457,7 @@ TEST(MinidumpMiscInfoWriter, TimeZoneStringsOverflow) { base::string16 standard_name_utf16 = base::UTF8ToUTF16(standard_name); c16lcpy(expected.TimeZone.StandardName, standard_name_utf16.c_str(), - ARRAYSIZE_UNSAFE(expected.TimeZone.StandardName)); + base::size(expected.TimeZone.StandardName)); memcpy(&expected.TimeZone.StandardDate, &kSystemTimeZero, sizeof(expected.TimeZone.StandardDate)); @@ -466,7 +465,7 @@ TEST(MinidumpMiscInfoWriter, TimeZoneStringsOverflow) { base::string16 daylight_name_utf16 = base::UTF8ToUTF16(daylight_name); c16lcpy(expected.TimeZone.DaylightName, daylight_name_utf16.c_str(), - ARRAYSIZE_UNSAFE(expected.TimeZone.DaylightName)); + base::size(expected.TimeZone.DaylightName)); memcpy(&expected.TimeZone.DaylightDate, &kSystemTimeZero, sizeof(expected.TimeZone.DaylightDate)); @@ -497,12 +496,12 @@ TEST(MinidumpMiscInfoWriter, BuildStrings) { base::string16 build_string_utf16 = base::UTF8ToUTF16(kBuildString); c16lcpy(expected.BuildString, build_string_utf16.c_str(), - ARRAYSIZE_UNSAFE(expected.BuildString)); + base::size(expected.BuildString)); base::string16 debug_build_string_utf16 = base::UTF8ToUTF16(kDebugBuildString); c16lcpy(expected.DbgBldStr, debug_build_string_utf16.c_str(), - ARRAYSIZE_UNSAFE(expected.DbgBldStr)); + base::size(expected.DbgBldStr)); ExpectMiscInfoEqual(&expected, observed); } @@ -516,8 +515,8 @@ TEST(MinidumpMiscInfoWriter, BuildStringsOverflow) { MINIDUMP_MISC_INFO_N tmp; ALLOW_UNUSED_LOCAL(tmp); - std::string build_string(ARRAYSIZE_UNSAFE(tmp.BuildString) + 1, 'B'); - std::string debug_build_string(ARRAYSIZE_UNSAFE(tmp.DbgBldStr), 'D'); + std::string build_string(base::size(tmp.BuildString) + 1, 'B'); + std::string debug_build_string(base::size(tmp.DbgBldStr), 'D'); misc_info_writer->SetBuildString(build_string, debug_build_string); @@ -534,12 +533,12 @@ TEST(MinidumpMiscInfoWriter, BuildStringsOverflow) { base::string16 build_string_utf16 = base::UTF8ToUTF16(build_string); c16lcpy(expected.BuildString, build_string_utf16.c_str(), - ARRAYSIZE_UNSAFE(expected.BuildString)); + base::size(expected.BuildString)); base::string16 debug_build_string_utf16 = base::UTF8ToUTF16(debug_build_string); c16lcpy(expected.DbgBldStr, debug_build_string_utf16.c_str(), - ARRAYSIZE_UNSAFE(expected.DbgBldStr)); + base::size(expected.DbgBldStr)); ExpectMiscInfoEqual(&expected, observed); } @@ -679,7 +678,7 @@ TEST(MinidumpMiscInfoWriter, Everything) { base::string16 standard_name_utf16 = base::UTF8ToUTF16(kStandardName); c16lcpy(expected.TimeZone.StandardName, standard_name_utf16.c_str(), - ARRAYSIZE_UNSAFE(expected.TimeZone.StandardName)); + base::size(expected.TimeZone.StandardName)); memcpy(&expected.TimeZone.StandardDate, &kSystemTimeZero, sizeof(expected.TimeZone.StandardDate)); @@ -687,7 +686,7 @@ TEST(MinidumpMiscInfoWriter, Everything) { base::string16 daylight_name_utf16 = base::UTF8ToUTF16(kDaylightName); c16lcpy(expected.TimeZone.DaylightName, daylight_name_utf16.c_str(), - ARRAYSIZE_UNSAFE(expected.TimeZone.DaylightName)); + base::size(expected.TimeZone.DaylightName)); memcpy(&expected.TimeZone.DaylightDate, &kSystemTimeZero, sizeof(expected.TimeZone.DaylightDate)); @@ -695,12 +694,12 @@ TEST(MinidumpMiscInfoWriter, Everything) { base::string16 build_string_utf16 = base::UTF8ToUTF16(kBuildString); c16lcpy(expected.BuildString, build_string_utf16.c_str(), - ARRAYSIZE_UNSAFE(expected.BuildString)); + base::size(expected.BuildString)); base::string16 debug_build_string_utf16 = base::UTF8ToUTF16(kDebugBuildString); c16lcpy(expected.DbgBldStr, debug_build_string_utf16.c_str(), - ARRAYSIZE_UNSAFE(expected.DbgBldStr)); + base::size(expected.DbgBldStr)); ExpectMiscInfoEqual(&expected, observed); } @@ -744,18 +743,18 @@ TEST(MinidumpMiscInfoWriter, InitializeFromSnapshot) { expect_misc_info.TimeZone.Bias = 300; c16lcpy(expect_misc_info.TimeZone.StandardName, standard_time_name_utf16.c_str(), - ARRAYSIZE_UNSAFE(expect_misc_info.TimeZone.StandardName)); + base::size(expect_misc_info.TimeZone.StandardName)); expect_misc_info.TimeZone.StandardBias = 0; c16lcpy(expect_misc_info.TimeZone.DaylightName, daylight_time_name_utf16.c_str(), - ARRAYSIZE_UNSAFE(expect_misc_info.TimeZone.DaylightName)); + base::size(expect_misc_info.TimeZone.DaylightName)); expect_misc_info.TimeZone.DaylightBias = -60; c16lcpy(expect_misc_info.BuildString, build_string_utf16.c_str(), - ARRAYSIZE_UNSAFE(expect_misc_info.BuildString)); + base::size(expect_misc_info.BuildString)); c16lcpy(expect_misc_info.DbgBldStr, debug_build_string_utf16.c_str(), - ARRAYSIZE_UNSAFE(expect_misc_info.DbgBldStr)); + base::size(expect_misc_info.DbgBldStr)); const timeval kStartTime = { static_cast(expect_misc_info.ProcessCreateTime), 0 }; diff --git a/minidump/minidump_module_crashpad_info_writer_test.cc b/minidump/minidump_module_crashpad_info_writer_test.cc index 63a9338c..ba4ab05b 100644 --- a/minidump/minidump_module_crashpad_info_writer_test.cc +++ b/minidump/minidump_module_crashpad_info_writer_test.cc @@ -19,6 +19,7 @@ #include +#include "base/stl_util.h" #include "gtest/gtest.h" #include "minidump/minidump_annotation_writer.h" #include "minidump/minidump_simple_string_dictionary_writer.h" @@ -154,9 +155,9 @@ TEST(MinidumpModuleCrashpadInfoWriter, FullModule) { sizeof(MinidumpSimpleStringDictionaryEntry) + sizeof(MinidumpAnnotationList) + 2 + // padding sizeof(MinidumpAnnotation) + sizeof(MinidumpUTF8String) + - arraysize(kEntry) + 2 + // padding - sizeof(MinidumpUTF8String) + arraysize(kKey) + - sizeof(MinidumpUTF8String) + arraysize(kValue) + + base::size(kEntry) + 2 + // padding + sizeof(MinidumpUTF8String) + base::size(kKey) + + sizeof(MinidumpUTF8String) + base::size(kValue) + sizeof(MinidumpUTF8String) + annotation.name.size() + 1 + sizeof(MinidumpByteArray) + annotation.value.size()); diff --git a/minidump/minidump_module_writer.cc b/minidump/minidump_module_writer.cc index 305d37c4..6e533524 100644 --- a/minidump/minidump_module_writer.cc +++ b/minidump/minidump_module_writer.cc @@ -31,8 +31,7 @@ namespace crashpad { -MinidumpModuleCodeViewRecordWriter::~MinidumpModuleCodeViewRecordWriter() { -} +MinidumpModuleCodeViewRecordWriter::~MinidumpModuleCodeViewRecordWriter() {} namespace internal { @@ -45,8 +44,7 @@ MinidumpModuleCodeViewRecordPDBLinkWriter< template MinidumpModuleCodeViewRecordPDBLinkWriter< - CodeViewRecordType>::~MinidumpModuleCodeViewRecordPDBLinkWriter() { -} + CodeViewRecordType>::~MinidumpModuleCodeViewRecordPDBLinkWriter() {} template size_t @@ -82,8 +80,7 @@ template class internal::MinidumpModuleCodeViewRecordPDBLinkWriter< CodeViewRecordPDB20>; MinidumpModuleCodeViewRecordPDB20Writer:: - ~MinidumpModuleCodeViewRecordPDB20Writer() { -} + ~MinidumpModuleCodeViewRecordPDB20Writer() {} void MinidumpModuleCodeViewRecordPDB20Writer::SetTimestampAndAge( time_t timestamp, @@ -100,8 +97,7 @@ template class internal::MinidumpModuleCodeViewRecordPDBLinkWriter< CodeViewRecordPDB70>; MinidumpModuleCodeViewRecordPDB70Writer:: - ~MinidumpModuleCodeViewRecordPDB70Writer() { -} + ~MinidumpModuleCodeViewRecordPDB70Writer() {} void MinidumpModuleCodeViewRecordPDB70Writer::InitializeFromSnapshot( const ModuleSnapshot* module_snapshot) { @@ -115,15 +111,52 @@ void MinidumpModuleCodeViewRecordPDB70Writer::InitializeFromSnapshot( SetUUIDAndAge(uuid, age); } +MinidumpModuleCodeViewRecordBuildIDWriter:: + MinidumpModuleCodeViewRecordBuildIDWriter() + : MinidumpModuleCodeViewRecordWriter(), build_id_() {} + +MinidumpModuleCodeViewRecordBuildIDWriter:: + ~MinidumpModuleCodeViewRecordBuildIDWriter() {} + +size_t MinidumpModuleCodeViewRecordBuildIDWriter::SizeOfObject() { + DCHECK_GE(state(), kStateFrozen); + return offsetof(CodeViewRecordBuildID, build_id) + build_id_.size(); +} + +void MinidumpModuleCodeViewRecordBuildIDWriter::SetBuildID( + const std::vector& build_id) { + DCHECK_EQ(state(), kStateMutable); + build_id_ = build_id; +} + +bool MinidumpModuleCodeViewRecordBuildIDWriter::WriteObject( + FileWriterInterface* file_writer) { + DCHECK_EQ(state(), kStateWritable); + + CodeViewRecordBuildID cv; + cv.signature = CodeViewRecordBuildID::kSignature; + + WritableIoVec iov; + iov.iov_base = &cv; + iov.iov_len = offsetof(CodeViewRecordBuildID, build_id); + std::vector iovecs(1, iov); + + if (!build_id_.empty()) { + iov.iov_base = build_id_.data(); + iov.iov_len = build_id_.size(); + iovecs.push_back(iov); + } + + return file_writer->WriteIoVec(&iovecs); +} + MinidumpModuleMiscDebugRecordWriter::MinidumpModuleMiscDebugRecordWriter() : internal::MinidumpWritable(), image_debug_misc_(), data_(), - data_utf16_() { -} + data_utf16_() {} -MinidumpModuleMiscDebugRecordWriter::~MinidumpModuleMiscDebugRecordWriter() { -} +MinidumpModuleMiscDebugRecordWriter::~MinidumpModuleMiscDebugRecordWriter() {} void MinidumpModuleMiscDebugRecordWriter::SetData(const std::string& data, bool utf16) { @@ -203,8 +236,7 @@ MinidumpModuleWriter::MinidumpModuleWriter() module_.VersionInfo.dwStrucVersion = VS_FFI_STRUCVERSION; } -MinidumpModuleWriter::~MinidumpModuleWriter() { -} +MinidumpModuleWriter::~MinidumpModuleWriter() {} void MinidumpModuleWriter::InitializeFromSnapshot( const ModuleSnapshot* module_snapshot) { @@ -242,9 +274,21 @@ void MinidumpModuleWriter::InitializeFromSnapshot( } SetFileTypeAndSubtype(file_type, VFT2_UNKNOWN); - auto codeview_record = - std::make_unique(); - codeview_record->InitializeFromSnapshot(module_snapshot); + auto build_id = module_snapshot->BuildID(); + + std::unique_ptr codeview_record; + if (!build_id.empty()) { + auto cv_record_build_id = + std::make_unique(); + cv_record_build_id->SetBuildID(build_id); + codeview_record = std::move(cv_record_build_id); + } else { + auto cv_record_pdb70 = + std::make_unique(); + cv_record_pdb70->InitializeFromSnapshot(module_snapshot); + codeview_record = std::move(cv_record_pdb70); + } + SetCodeViewRecord(std::move(codeview_record)); } @@ -372,11 +416,9 @@ bool MinidumpModuleWriter::WriteObject(FileWriterInterface* file_writer) { } MinidumpModuleListWriter::MinidumpModuleListWriter() - : MinidumpStreamWriter(), modules_(), module_list_base_() { -} + : MinidumpStreamWriter(), modules_(), module_list_base_() {} -MinidumpModuleListWriter::~MinidumpModuleListWriter() { -} +MinidumpModuleListWriter::~MinidumpModuleListWriter() {} void MinidumpModuleListWriter::InitializeFromSnapshot( const std::vector& module_snapshots) { diff --git a/minidump/minidump_module_writer.h b/minidump/minidump_module_writer.h index 555c4115..b328bf04 100644 --- a/minidump/minidump_module_writer.h +++ b/minidump/minidump_module_writer.h @@ -88,7 +88,8 @@ class MinidumpModuleCodeViewRecordPDBLinkWriter //! \brief The writer for a CodeViewRecordPDB20 object in a minidump file. //! -//! Most users will want MinidumpModuleCodeViewRecordPDB70Writer instead. +//! Most users will want MinidumpModuleCodeViewRecordPDB70Writer or +//! MinidumpModuleCodeViewRecordBuildIDWriter instead. class MinidumpModuleCodeViewRecordPDB20Writer final : public internal::MinidumpModuleCodeViewRecordPDBLinkWriter< CodeViewRecordPDB20> { @@ -136,6 +137,26 @@ class MinidumpModuleCodeViewRecordPDB70Writer final DISALLOW_COPY_AND_ASSIGN(MinidumpModuleCodeViewRecordPDB70Writer); }; +//! \brief The writer for a CodeViewRecordBuildID object in a minidump file. +class MinidumpModuleCodeViewRecordBuildIDWriter final + : public MinidumpModuleCodeViewRecordWriter { + public: + MinidumpModuleCodeViewRecordBuildIDWriter(); + ~MinidumpModuleCodeViewRecordBuildIDWriter() override; + + //! \brief Sets the build ID used for symbol lookup. + void SetBuildID(const std::vector& build_id); + + private: + // MinidumpWritable: + size_t SizeOfObject() override; + bool WriteObject(FileWriterInterface* file_writer) override; + + std::vector build_id_; + + DISALLOW_COPY_AND_ASSIGN(MinidumpModuleCodeViewRecordBuildIDWriter); +}; + //! \brief The writer for an IMAGE_DEBUG_MISC object in a minidump file. //! //! Most users will want MinidumpModuleCodeViewRecordPDB70Writer instead. diff --git a/minidump/minidump_module_writer_test.cc b/minidump/minidump_module_writer_test.cc index 0fddfe88..71db3ac1 100644 --- a/minidump/minidump_module_writer_test.cc +++ b/minidump/minidump_module_writer_test.cc @@ -20,6 +20,7 @@ #include #include "base/format_macros.h" +#include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "gtest/gtest.h" @@ -264,6 +265,61 @@ void ExpectModule(const MINIDUMP_MODULE* expected, expected_debug_utf16)); } +// ExpectModuleWithBuildIDCv() is like ExpectModule( but expects the module to +// have a BuildID CodeView Record. +void ExpectModuleWithBuildIDCv(const MINIDUMP_MODULE* expected, + const MINIDUMP_MODULE* observed, + const std::string& file_contents, + const std::string& expected_module_name, + const std::vector& expected_build_id) { + EXPECT_EQ(observed->BaseOfImage, expected->BaseOfImage); + EXPECT_EQ(observed->SizeOfImage, expected->SizeOfImage); + EXPECT_EQ(observed->CheckSum, expected->CheckSum); + EXPECT_EQ(observed->TimeDateStamp, expected->TimeDateStamp); + EXPECT_EQ(observed->VersionInfo.dwSignature, + implicit_cast(VS_FFI_SIGNATURE)); + EXPECT_EQ(observed->VersionInfo.dwStrucVersion, + implicit_cast(VS_FFI_STRUCVERSION)); + EXPECT_EQ(observed->VersionInfo.dwFileVersionMS, + expected->VersionInfo.dwFileVersionMS); + EXPECT_EQ(observed->VersionInfo.dwFileVersionLS, + expected->VersionInfo.dwFileVersionLS); + EXPECT_EQ(observed->VersionInfo.dwProductVersionMS, + expected->VersionInfo.dwProductVersionMS); + EXPECT_EQ(observed->VersionInfo.dwProductVersionLS, + expected->VersionInfo.dwProductVersionLS); + EXPECT_EQ(observed->VersionInfo.dwFileFlagsMask, + expected->VersionInfo.dwFileFlagsMask); + EXPECT_EQ(observed->VersionInfo.dwFileFlags, + expected->VersionInfo.dwFileFlags); + EXPECT_EQ(observed->VersionInfo.dwFileOS, expected->VersionInfo.dwFileOS); + EXPECT_EQ(observed->VersionInfo.dwFileType, expected->VersionInfo.dwFileType); + EXPECT_EQ(observed->VersionInfo.dwFileSubtype, + expected->VersionInfo.dwFileSubtype); + EXPECT_EQ(observed->VersionInfo.dwFileDateMS, + expected->VersionInfo.dwFileDateMS); + EXPECT_EQ(observed->VersionInfo.dwFileDateLS, + expected->VersionInfo.dwFileDateLS); + EXPECT_EQ(observed->Reserved0, 0u); + EXPECT_EQ(observed->Reserved1, 0u); + + EXPECT_NE(observed->ModuleNameRva, 0u); + base::string16 observed_module_name_utf16 = + MinidumpStringAtRVAAsString(file_contents, observed->ModuleNameRva); + base::string16 expected_module_name_utf16 = + base::UTF8ToUTF16(expected_module_name); + EXPECT_EQ(observed_module_name_utf16, expected_module_name_utf16); + + const CodeViewRecordBuildID* codeview_build_id_record = + MinidumpWritableAtLocationDescriptor( + file_contents, observed->CvRecord); + ASSERT_TRUE(codeview_build_id_record); + EXPECT_EQ(memcmp(expected_build_id.data(), + &codeview_build_id_record->build_id, + expected_build_id.size()), + 0); +} + TEST(MinidumpModuleWriter, EmptyModule) { MinidumpFileWriter minidump_file_writer; auto module_list_writer = std::make_unique(); @@ -324,9 +380,22 @@ TEST(MinidumpModuleWriter, OneModule) { constexpr uint32_t kFileType = VFT_DRV; constexpr uint32_t kFileSubtype = VFT2_DRV_KEYBOARD; static constexpr char kPDBName[] = "statical.pdb"; - static constexpr uint8_t kPDBUUIDBytes[16] = - {0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10, - 0x08, 0x19, 0x2a, 0x3b, 0x4c, 0x5d, 0x6e, 0x7f}; + static constexpr uint8_t kPDBUUIDBytes[16] = {0xfe, + 0xdc, + 0xba, + 0x98, + 0x76, + 0x54, + 0x32, + 0x10, + 0x08, + 0x19, + 0x2a, + 0x3b, + 0x4c, + 0x5d, + 0x6e, + 0x7f}; UUID pdb_uuid; pdb_uuid.InitializeFromBytes(kPDBUUIDBytes); constexpr uint32_t kPDBAge = 1; @@ -470,6 +539,50 @@ TEST(MinidumpModuleWriter, OneModule_CodeViewUsesPDB20_MiscUsesUTF16) { kDebugUTF16)); } +TEST(MinidumpModuleWriter, OneModule_CodeViewBuildID) { + // MinidumpModuleWriter.OneModule tested with a BuildID CodeView + MinidumpFileWriter minidump_file_writer; + auto module_list_writer = std::make_unique(); + + static constexpr char kModuleName[] = "dinosaur"; + static constexpr char kBuildID[] = + "averylonghashcodeormaybeitsjustrandomnumbershardtosay"; + + std::vector build_id_data(kBuildID, kBuildID + 53); + + auto module_writer = std::make_unique(); + module_writer->SetName(kModuleName); + + auto codeview_build_id_writer = + std::make_unique(); + codeview_build_id_writer->SetBuildID(build_id_data); + module_writer->SetCodeViewRecord(std::move(codeview_build_id_writer)); + + module_list_writer->AddModule(std::move(module_writer)); + ASSERT_TRUE(minidump_file_writer.AddStream(std::move(module_list_writer))); + + StringFile string_file; + ASSERT_TRUE(minidump_file_writer.WriteEverything(&string_file)); + + ASSERT_GT(string_file.string().size(), + sizeof(MINIDUMP_HEADER) + sizeof(MINIDUMP_DIRECTORY) + + sizeof(MINIDUMP_MODULE_LIST) + 1 * sizeof(MINIDUMP_MODULE)); + + const MINIDUMP_MODULE_LIST* module_list = nullptr; + ASSERT_NO_FATAL_FAILURE( + GetModuleListStream(string_file.string(), &module_list)); + + EXPECT_EQ(module_list->NumberOfModules, 1u); + + MINIDUMP_MODULE expected = {}; + + ASSERT_NO_FATAL_FAILURE(ExpectModuleWithBuildIDCv(&expected, + &module_list->Modules[0], + string_file.string(), + kModuleName, + build_id_data)); +} + TEST(MinidumpModuleWriter, ThreeModules) { // As good exercise, this test uses three modules, one with a PDB 7.0 link as // its CodeView record, one with no CodeView record, and one with a PDB 2.0 @@ -481,9 +594,22 @@ TEST(MinidumpModuleWriter, ThreeModules) { constexpr uint64_t kModuleBase0 = 0x100101000; constexpr uint32_t kModuleSize0 = 0xf000; static constexpr char kPDBName0[] = "main"; - static constexpr uint8_t kPDBUUIDBytes0[16] = - {0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x11, - 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99}; + static constexpr uint8_t kPDBUUIDBytes0[16] = {0xaa, + 0xbb, + 0xcc, + 0xdd, + 0xee, + 0xff, + 0x00, + 0x11, + 0x22, + 0x33, + 0x44, + 0x55, + 0x66, + 0x77, + 0x88, + 0x99}; UUID pdb_uuid_0; pdb_uuid_0.InitializeFromBytes(kPDBUUIDBytes0); constexpr uint32_t kPDBAge0 = 0; @@ -650,10 +776,10 @@ void InitializeTestModuleSnapshotFromMinidumpModule( TEST(MinidumpModuleWriter, InitializeFromSnapshot) { MINIDUMP_MODULE expect_modules[3] = {}; - const char* module_paths[arraysize(expect_modules)] = {}; - const char* module_pdbs[arraysize(expect_modules)] = {}; - UUID uuids[arraysize(expect_modules)] = {}; - uint32_t ages[arraysize(expect_modules)] = {}; + const char* module_paths[base::size(expect_modules)] = {}; + const char* module_pdbs[base::size(expect_modules)] = {}; + UUID uuids[base::size(expect_modules)] = {}; + uint32_t ages[base::size(expect_modules)] = {}; expect_modules[0].BaseOfImage = 0x100101000; expect_modules[0].SizeOfImage = 0xf000; @@ -665,9 +791,22 @@ TEST(MinidumpModuleWriter, InitializeFromSnapshot) { expect_modules[0].VersionInfo.dwFileType = VFT_APP; module_paths[0] = "/usr/bin/true"; module_pdbs[0] = "true"; - static constexpr uint8_t kUUIDBytes0[16] = - {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, - 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}; + static constexpr uint8_t kUUIDBytes0[16] = {0x00, + 0x11, + 0x22, + 0x33, + 0x44, + 0x55, + 0x66, + 0x77, + 0x88, + 0x99, + 0xaa, + 0xbb, + 0xcc, + 0xdd, + 0xee, + 0xff}; uuids[0].InitializeFromBytes(kUUIDBytes0); ages[0] = 10; @@ -681,9 +820,22 @@ TEST(MinidumpModuleWriter, InitializeFromSnapshot) { expect_modules[1].VersionInfo.dwFileType = VFT_DLL; module_paths[1] = "/usr/lib/libSystem.B.dylib"; module_pdbs[1] = "libSystem.B.dylib.pdb"; - static constexpr uint8_t kUUIDBytes1[16] = - {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, - 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}; + static constexpr uint8_t kUUIDBytes1[16] = {0x00, + 0x01, + 0x02, + 0x03, + 0x04, + 0x05, + 0x06, + 0x07, + 0x08, + 0x09, + 0x0a, + 0x0b, + 0x0c, + 0x0d, + 0x0e, + 0x0f}; uuids[1].InitializeFromBytes(kUUIDBytes1); ages[1] = 20; @@ -697,15 +849,28 @@ TEST(MinidumpModuleWriter, InitializeFromSnapshot) { expect_modules[2].VersionInfo.dwFileType = VFT_UNKNOWN; module_paths[2] = "/usr/lib/dyld"; module_pdbs[2] = "/usr/lib/dyld.pdb"; - static constexpr uint8_t kUUIDBytes2[16] = - {0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, - 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0}; + static constexpr uint8_t kUUIDBytes2[16] = {0xff, + 0xfe, + 0xfd, + 0xfc, + 0xfb, + 0xfa, + 0xf9, + 0xf8, + 0xf7, + 0xf6, + 0xf5, + 0xf4, + 0xf3, + 0xf2, + 0xf1, + 0xf0}; uuids[2].InitializeFromBytes(kUUIDBytes2); ages[2] = 30; std::vector> module_snapshots_owner; std::vector module_snapshots; - for (size_t index = 0; index < arraysize(expect_modules); ++index) { + for (size_t index = 0; index < base::size(expect_modules); ++index) { module_snapshots_owner.push_back(std::make_unique()); TestModuleSnapshot* module_snapshot = module_snapshots_owner.back().get(); InitializeTestModuleSnapshotFromMinidumpModule(module_snapshot, diff --git a/minidump/minidump_rva_list_writer_test.cc b/minidump/minidump_rva_list_writer_test.cc index 30432871..38074324 100644 --- a/minidump/minidump_rva_list_writer_test.cc +++ b/minidump/minidump_rva_list_writer_test.cc @@ -17,6 +17,7 @@ #include #include "base/format_macros.h" +#include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "gtest/gtest.h" #include "minidump/test/minidump_rva_list_test_util.h" @@ -86,10 +87,10 @@ TEST(MinidumpRVAListWriter, ThreeChildren) { ASSERT_TRUE(list_writer.WriteEverything(&string_file)); const MinidumpRVAList* list = - MinidumpRVAListAtStart(string_file.string(), arraysize(kValues)); + MinidumpRVAListAtStart(string_file.string(), base::size(kValues)); ASSERT_TRUE(list); - for (size_t index = 0; index < arraysize(kValues); ++index) { + for (size_t index = 0; index < base::size(kValues); ++index) { SCOPED_TRACE(base::StringPrintf("index %" PRIuS, index)); const uint32_t* child = MinidumpWritableAtRVA( diff --git a/minidump/minidump_string_writer_test.cc b/minidump/minidump_string_writer_test.cc index 382baaf7..ddb8c486 100644 --- a/minidump/minidump_string_writer_test.cc +++ b/minidump/minidump_string_writer_test.cc @@ -18,6 +18,7 @@ #include "base/compiler_specific.h" #include "base/format_macros.h" +#include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "gtest/gtest.h" @@ -66,15 +67,16 @@ TEST(MinidumpStringWriter, MinidumpUTF16StringWriter) { {4, "\360\220\204\202", 2, {0xd800, 0xdd02}}, // 𐄂 (non-BMP) }; - for (size_t index = 0; index < arraysize(kTestData); ++index) { + for (size_t index = 0; index < base::size(kTestData); ++index) { SCOPED_TRACE(base::StringPrintf( "index %" PRIuS ", input %s", index, kTestData[index].input_string)); // Make sure that the expected output string with its NUL terminator fits in // the space provided. - ASSERT_EQ(kTestData[index] - .output_string[arraysize(kTestData[index].output_string) - 1], - 0); + ASSERT_EQ( + kTestData[index] + .output_string[base::size(kTestData[index].output_string) - 1], + 0); string_file.Reset(); crashpad::internal::MinidumpUTF16StringWriter string_writer; @@ -100,6 +102,12 @@ TEST(MinidumpStringWriter, MinidumpUTF16StringWriter) { } } +// Related tracking issues: +// https://fuchsia.atlassian.net/browse/DX-487 +// https://bugs.chromium.org/p/chromium/issues/detail?id=872892 +// https://bugs.chromium.org/p/chromium/issues/detail?id=889582 +// TODO: Re-enable test once LUCI supports invalid UTF8 characters in test logs. +#if !defined(CRASHPAD_IS_IN_FUCHSIA) TEST(MinidumpStringWriter, ConvertInvalidUTF8ToUTF16) { StringFile string_file; @@ -112,7 +120,7 @@ TEST(MinidumpStringWriter, ConvertInvalidUTF8ToUTF16) { "\303\0\251", // NUL in middle of valid sequence }; - for (size_t index = 0; index < arraysize(kTestData); ++index) { + for (size_t index = 0; index < base::size(kTestData); ++index) { SCOPED_TRACE(base::StringPrintf( "index %" PRIuS ", input %s", index, kTestData[index])); string_file.Reset(); @@ -139,6 +147,7 @@ TEST(MinidumpStringWriter, ConvertInvalidUTF8ToUTF16) { EXPECT_NE(output_string.find(0xfffd), base::string16::npos); } } +#endif // !defined(CRASHPAD_IS_IN_FUCHSIA) TEST(MinidumpStringWriter, MinidumpUTF8StringWriter) { StringFile string_file; @@ -174,7 +183,7 @@ TEST(MinidumpStringWriter, MinidumpUTF8StringWriter) { {4, "\360\220\204\202"}, // 𐄂 (non-BMP) }; - for (size_t index = 0; index < arraysize(kTestData); ++index) { + for (size_t index = 0; index < base::size(kTestData); ++index) { SCOPED_TRACE(base::StringPrintf( "index %" PRIuS ", input %s", index, kTestData[index].string)); diff --git a/minidump/minidump_system_info_writer.cc b/minidump/minidump_system_info_writer.cc index cc87d242..7b4b49dc 100644 --- a/minidump/minidump_system_info_writer.cc +++ b/minidump/minidump_system_info_writer.cc @@ -17,10 +17,11 @@ #include #include "base/logging.h" +#include "base/stl_util.h" #include "minidump/minidump_string_writer.h" #include "snapshot/system_snapshot.h" #include "util/file/file_writer.h" -#include "util/misc/arraysize_unsafe.h" +#include "util/misc/arraysize.h" #include "util/misc/implicit_cast.h" namespace crashpad { @@ -150,7 +151,7 @@ void MinidumpSystemInfoWriter::InitializeFromSnapshot( SetCPUX86VersionAndFeatures(system_snapshot->CPUX86Signature(), system_snapshot->CPUX86Features() & 0xffffffff); - if (cpu_vendor == "AuthenticAMD") { + if (cpu_vendor == "AuthenticAMD" || cpu_vendor == "HygonGenuine") { SetCPUX86AMDExtendedFeatures( system_snapshot->CPUX86ExtendedFeatures() & 0xffffffff); } @@ -175,6 +176,9 @@ void MinidumpSystemInfoWriter::InitializeFromSnapshot( case SystemSnapshot::kOperatingSystemFuchsia: operating_system = kMinidumpOSFuchsia; break; + case SystemSnapshot::kOperatingSystemIOS: + operating_system = kMinidumpOSIOS; + break; default: NOTREACHED(); operating_system = kMinidumpOSUnknown; @@ -212,7 +216,7 @@ void MinidumpSystemInfoWriter::SetCPUX86Vendor(uint32_t ebx, system_info_.ProcessorArchitecture == kMinidumpCPUArchitectureX86Win64); - static_assert(ARRAYSIZE_UNSAFE(system_info_.Cpu.X86CpuInfo.VendorId) == 3, + static_assert(ArraySize(system_info_.Cpu.X86CpuInfo.VendorId) == 3, "VendorId must have 3 elements"); system_info_.Cpu.X86CpuInfo.VendorId[0] = ebx; @@ -230,7 +234,7 @@ void MinidumpSystemInfoWriter::SetCPUX86VendorString( sizeof(registers) == sizeof(system_info_.Cpu.X86CpuInfo.VendorId), "VendorId sizes must be equal"); - for (size_t index = 0; index < arraysize(registers); ++index) { + for (size_t index = 0; index < base::size(registers); ++index) { memcpy(®isters[index], &vendor[index * sizeof(*registers)], sizeof(*registers)); @@ -270,9 +274,8 @@ void MinidumpSystemInfoWriter::SetCPUOtherFeatures(uint64_t features_0, system_info_.ProcessorArchitecture != kMinidumpCPUArchitectureX86Win64); - static_assert( - ARRAYSIZE_UNSAFE(system_info_.Cpu.OtherCpuInfo.ProcessorFeatures) == 2, - "ProcessorFeatures must have 2 elements"); + static_assert(ArraySize(system_info_.Cpu.OtherCpuInfo.ProcessorFeatures) == 2, + "ProcessorFeatures must have 2 elements"); system_info_.Cpu.OtherCpuInfo.ProcessorFeatures[0] = features_0; system_info_.Cpu.OtherCpuInfo.ProcessorFeatures[1] = features_1; diff --git a/minidump/minidump_thread_id_map_test.cc b/minidump/minidump_thread_id_map_test.cc index 7c6795a3..548729e6 100644 --- a/minidump/minidump_thread_id_map_test.cc +++ b/minidump/minidump_thread_id_map_test.cc @@ -19,6 +19,7 @@ #include #include "base/macros.h" +#include "base/stl_util.h" #include "gtest/gtest.h" #include "snapshot/test/test_thread_snapshot.h" @@ -38,7 +39,8 @@ class MinidumpThreadIDMapTest : public testing::Test { // testing::Test: void SetUp() override { - for (size_t index = 0; index < arraysize(test_thread_snapshots_); ++index) { + for (size_t index = 0; index < base::size(test_thread_snapshots_); + ++index) { thread_snapshots_.push_back(&test_thread_snapshots_[index]); } } @@ -59,7 +61,7 @@ class MinidumpThreadIDMapTest : public testing::Test { } void SetThreadID(size_t index, uint64_t thread_id) { - ASSERT_LT(index, arraysize(test_thread_snapshots_)); + ASSERT_LT(index, base::size(test_thread_snapshots_)); test_thread_snapshots_[index].SetThreadID(thread_id); } diff --git a/minidump/minidump_thread_writer_test.cc b/minidump/minidump_thread_writer_test.cc index e95c9044..956d1005 100644 --- a/minidump/minidump_thread_writer_test.cc +++ b/minidump/minidump_thread_writer_test.cc @@ -19,6 +19,7 @@ #include "base/compiler_specific.h" #include "base/format_macros.h" +#include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "gtest/gtest.h" #include "minidump/minidump_context_writer.h" @@ -522,10 +523,10 @@ template void RunInitializeFromSnapshotTest(bool thread_id_collision) { using MinidumpContextType = typename Traits::MinidumpContextType; MINIDUMP_THREAD expect_threads[3] = {}; - uint64_t thread_ids[arraysize(expect_threads)] = {}; - uint8_t memory_values[arraysize(expect_threads)] = {}; - uint32_t context_seeds[arraysize(expect_threads)] = {}; - MINIDUMP_MEMORY_DESCRIPTOR tebs[arraysize(expect_threads)] = {}; + uint64_t thread_ids[base::size(expect_threads)] = {}; + uint8_t memory_values[base::size(expect_threads)] = {}; + uint32_t context_seeds[base::size(expect_threads)] = {}; + MINIDUMP_MEMORY_DESCRIPTOR tebs[base::size(expect_threads)] = {}; constexpr size_t kTebSize = 1024; @@ -581,7 +582,7 @@ void RunInitializeFromSnapshotTest(bool thread_id_collision) { std::vector> thread_snapshots_owner; std::vector thread_snapshots; - for (size_t index = 0; index < arraysize(expect_threads); ++index) { + for (size_t index = 0; index < base::size(expect_threads); ++index) { thread_snapshots_owner.push_back(std::make_unique()); TestThreadSnapshot* thread_snapshot = thread_snapshots_owner.back().get(); diff --git a/minidump/minidump_unloaded_module_writer.cc b/minidump/minidump_unloaded_module_writer.cc index 855e1967..c8a5f0fd 100644 --- a/minidump/minidump_unloaded_module_writer.cc +++ b/minidump/minidump_unloaded_module_writer.cc @@ -123,7 +123,8 @@ void MinidumpUnloadedModuleListWriter::InitializeFromSnapshot( DCHECK_EQ(state(), kStateMutable); DCHECK(unloaded_modules_.empty()); - for (auto unloaded_module_snapshot : unloaded_module_snapshots) { + for (const UnloadedModuleSnapshot& unloaded_module_snapshot : + unloaded_module_snapshots) { auto unloaded_module = std::make_unique(); unloaded_module->InitializeFromSnapshot(unloaded_module_snapshot); AddUnloadedModule(std::move(unloaded_module)); diff --git a/minidump/minidump_user_stream_writer.cc b/minidump/minidump_user_stream_writer.cc index 139e8270..8401b285 100644 --- a/minidump/minidump_user_stream_writer.cc +++ b/minidump/minidump_user_stream_writer.cc @@ -42,7 +42,7 @@ class MinidumpUserStreamWriter::SnapshotContentsWriter final return snapshot_->Read(this); } - size_t GetSize() const override { return snapshot_ ? snapshot_->Size() : 0; }; + size_t GetSize() const override { return snapshot_ ? snapshot_->Size() : 0; } bool MemorySnapshotDelegateRead(void* data, size_t size) override { return writer_->Write(data, size); diff --git a/minidump/minidump_writable.cc b/minidump/minidump_writable.cc index d2b58f5b..fa3e2f16 100644 --- a/minidump/minidump_writable.cc +++ b/minidump/minidump_writable.cc @@ -17,6 +17,7 @@ #include #include "base/logging.h" +#include "base/stl_util.h" #include "util/file/file_writer.h" #include "util/numeric/safe_assignment.h" @@ -244,7 +245,7 @@ bool MinidumpWritable::WritePaddingAndObject(FileWriterInterface* file_writer) { // The number of elements in kZeroes must be at least one less than the // maximum Alignment() ever encountered. static constexpr uint8_t kZeroes[kMaximumAlignment - 1] = {}; - DCHECK_LE(leading_pad_bytes_, arraysize(kZeroes)); + DCHECK_LE(leading_pad_bytes_, base::size(kZeroes)); if (leading_pad_bytes_) { if (!file_writer->Write(&kZeroes, leading_pad_bytes_)) { diff --git a/minidump/test/minidump_context_test_util.cc b/minidump/test/minidump_context_test_util.cc index 28f94106..c94fa20d 100644 --- a/minidump/test/minidump_context_test_util.cc +++ b/minidump/test/minidump_context_test_util.cc @@ -18,7 +18,7 @@ #include #include "base/format_macros.h" -#include "base/macros.h" +#include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "gtest/gtest.h" #include "snapshot/cpu_context.h" @@ -128,7 +128,8 @@ void InitializeMinidumpContextAMD64(MinidumpContextAMD64* context, context->ds = static_cast(value++); context->es = static_cast(value++); context->ss = static_cast(value++); - for (size_t index = 0; index < arraysize(context->vector_register); ++index) { + for (size_t index = 0; index < base::size(context->vector_register); + ++index) { context->vector_register[index].lo = value++; context->vector_register[index].hi = value++; } @@ -151,7 +152,7 @@ void InitializeMinidumpContextARM(MinidumpContextARM* context, uint32_t seed) { uint32_t value = seed; - for (size_t index = 0; index < arraysize(context->regs); ++index) { + for (size_t index = 0; index < base::size(context->regs); ++index) { context->regs[index] = value++; } context->fp = value++; @@ -162,7 +163,7 @@ void InitializeMinidumpContextARM(MinidumpContextARM* context, uint32_t seed) { context->pc = value++; context->cpsr = value++; - for (size_t index = 0; index < arraysize(context->vfp); ++index) { + for (size_t index = 0; index < base::size(context->vfp); ++index) { context->vfp[index] = value++; } context->fpscr = value++; @@ -176,18 +177,20 @@ void InitializeMinidumpContextARM64(MinidumpContextARM64* context, return; } - context->context_flags = kMinidumpContextARM64All; + context->context_flags = kMinidumpContextARM64Full; uint32_t value = seed; - for (size_t index = 0; index < arraysize(context->regs); ++index) { + for (size_t index = 0; index < base::size(context->regs); ++index) { context->regs[index] = value++; } + context->fp = value++; + context->lr = value++; context->sp = value++; context->pc = value++; context->cpsr = value++; - for (size_t index = 0; index < arraysize(context->fpsimd); ++index) { + for (size_t index = 0; index < base::size(context->fpsimd); ++index) { context->fpsimd[index].lo = value++; context->fpsimd[index].hi = value++; } @@ -207,7 +210,7 @@ void InitializeMinidumpContextMIPS(MinidumpContextMIPS* context, uint32_t value = seed; - for (size_t index = 0; index < arraysize(context->regs); ++index) { + for (size_t index = 0; index < base::size(context->regs); ++index) { context->regs[index] = value++; } @@ -218,7 +221,7 @@ void InitializeMinidumpContextMIPS(MinidumpContextMIPS* context, context->status = value++; context->cause = value++; - for (size_t index = 0; index < arraysize(context->fpregs.fregs); ++index) { + for (size_t index = 0; index < base::size(context->fpregs.fregs); ++index) { context->fpregs.fregs[index]._fp_fregs = static_cast(value++); } @@ -245,7 +248,7 @@ void InitializeMinidumpContextMIPS64(MinidumpContextMIPS64* context, uint64_t value = seed; - for (size_t index = 0; index < arraysize(context->regs); ++index) { + for (size_t index = 0; index < base::size(context->regs); ++index) { context->regs[index] = value++; } @@ -256,7 +259,7 @@ void InitializeMinidumpContextMIPS64(MinidumpContextMIPS64* context, context->status = value++; context->cause = value++; - for (size_t index = 0; index < arraysize(context->fpregs.dregs); ++index) { + for (size_t index = 0; index < base::size(context->fpregs.dregs); ++index) { context->fpregs.dregs[index] = static_cast(value++); } context->fpcsr = value++; @@ -291,35 +294,33 @@ void ExpectMinidumpContextFxsave(const FxsaveType* expected, EXPECT_EQ(observed->reserved_3, expected->reserved_3); EXPECT_EQ(observed->mxcsr, expected->mxcsr); EXPECT_EQ(observed->mxcsr_mask, expected->mxcsr_mask); - for (size_t st_mm_index = 0; - st_mm_index < arraysize(expected->st_mm); + for (size_t st_mm_index = 0; st_mm_index < base::size(expected->st_mm); ++st_mm_index) { SCOPED_TRACE(base::StringPrintf("st_mm_index %" PRIuS, st_mm_index)); EXPECT_EQ(BytesToHexString(observed->st_mm[st_mm_index].st, - arraysize(observed->st_mm[st_mm_index].st)), + base::size(observed->st_mm[st_mm_index].st)), BytesToHexString(expected->st_mm[st_mm_index].st, - arraysize(expected->st_mm[st_mm_index].st))); + base::size(expected->st_mm[st_mm_index].st))); EXPECT_EQ( BytesToHexString(observed->st_mm[st_mm_index].st_reserved, - arraysize(observed->st_mm[st_mm_index].st_reserved)), + base::size(observed->st_mm[st_mm_index].st_reserved)), BytesToHexString(expected->st_mm[st_mm_index].st_reserved, - arraysize(expected->st_mm[st_mm_index].st_reserved))); + base::size(expected->st_mm[st_mm_index].st_reserved))); } - for (size_t xmm_index = 0; - xmm_index < arraysize(expected->xmm); + for (size_t xmm_index = 0; xmm_index < base::size(expected->xmm); ++xmm_index) { EXPECT_EQ(BytesToHexString(observed->xmm[xmm_index], - arraysize(observed->xmm[xmm_index])), + base::size(observed->xmm[xmm_index])), BytesToHexString(expected->xmm[xmm_index], - arraysize(expected->xmm[xmm_index]))) + base::size(expected->xmm[xmm_index]))) << "xmm_index " << xmm_index; } EXPECT_EQ( - BytesToHexString(observed->reserved_4, arraysize(observed->reserved_4)), - BytesToHexString(expected->reserved_4, arraysize(expected->reserved_4))); + BytesToHexString(observed->reserved_4, base::size(observed->reserved_4)), + BytesToHexString(expected->reserved_4, base::size(expected->reserved_4))); EXPECT_EQ( - BytesToHexString(observed->available, arraysize(observed->available)), - BytesToHexString(expected->available, arraysize(expected->available))); + BytesToHexString(observed->available, base::size(observed->available)), + BytesToHexString(expected->available, base::size(expected->available))); } } // namespace @@ -344,11 +345,11 @@ void ExpectMinidumpContextX86( EXPECT_EQ(observed->fsave.fpu_cs, expected.fsave.fpu_cs); EXPECT_EQ(observed->fsave.fpu_dp, expected.fsave.fpu_dp); EXPECT_EQ(observed->fsave.fpu_ds, expected.fsave.fpu_ds); - for (size_t index = 0; index < arraysize(expected.fsave.st); ++index) { + for (size_t index = 0; index < base::size(expected.fsave.st); ++index) { EXPECT_EQ(BytesToHexString(observed->fsave.st[index], - arraysize(observed->fsave.st[index])), + base::size(observed->fsave.st[index])), BytesToHexString(expected.fsave.st[index], - arraysize(expected.fsave.st[index]))) + base::size(expected.fsave.st[index]))) << "index " << index; } if (snapshot) { @@ -447,7 +448,8 @@ void ExpectMinidumpContextAMD64( ExpectMinidumpContextFxsave(&expected.fxsave, &observed->fxsave); - for (size_t index = 0; index < arraysize(expected.vector_register); ++index) { + for (size_t index = 0; index < base::size(expected.vector_register); + ++index) { if (snapshot) { EXPECT_EQ(observed->vector_register[index].lo, 0u) << "index " << index; EXPECT_EQ(observed->vector_register[index].hi, 0u) << "index " << index; @@ -487,7 +489,7 @@ void ExpectMinidumpContextARM(uint32_t expect_seed, EXPECT_EQ(observed->context_flags, expected.context_flags); - for (size_t index = 0; index < arraysize(expected.regs); ++index) { + for (size_t index = 0; index < base::size(expected.regs); ++index) { EXPECT_EQ(observed->regs[index], expected.regs[index]); } EXPECT_EQ(observed->fp, expected.fp); @@ -498,10 +500,10 @@ void ExpectMinidumpContextARM(uint32_t expect_seed, EXPECT_EQ(observed->cpsr, expected.cpsr); EXPECT_EQ(observed->fpscr, expected.fpscr); - for (size_t index = 0; index < arraysize(expected.vfp); ++index) { + for (size_t index = 0; index < base::size(expected.vfp); ++index) { EXPECT_EQ(observed->vfp[index], expected.vfp[index]); } - for (size_t index = 0; index < arraysize(expected.extra); ++index) { + for (size_t index = 0; index < base::size(expected.extra); ++index) { EXPECT_EQ(observed->extra[index], snapshot ? 0 : expected.extra[index]); } } @@ -514,14 +516,14 @@ void ExpectMinidumpContextARM64(uint32_t expect_seed, EXPECT_EQ(observed->context_flags, expected.context_flags); - for (size_t index = 0; index < arraysize(expected.regs); ++index) { + for (size_t index = 0; index < base::size(expected.regs); ++index) { EXPECT_EQ(observed->regs[index], expected.regs[index]); } EXPECT_EQ(observed->cpsr, expected.cpsr); EXPECT_EQ(observed->fpsr, expected.fpsr); EXPECT_EQ(observed->fpcr, expected.fpcr); - for (size_t index = 0; index < arraysize(expected.fpsimd); ++index) { + for (size_t index = 0; index < base::size(expected.fpsimd); ++index) { EXPECT_EQ(observed->fpsimd[index].lo, expected.fpsimd[index].lo); EXPECT_EQ(observed->fpsimd[index].hi, expected.fpsimd[index].hi); } @@ -535,7 +537,7 @@ void ExpectMinidumpContextMIPS(uint32_t expect_seed, EXPECT_EQ(observed->context_flags, expected.context_flags); - for (size_t index = 0; index < arraysize(expected.regs); ++index) { + for (size_t index = 0; index < base::size(expected.regs); ++index) { EXPECT_EQ(observed->regs[index], expected.regs[index]); } @@ -546,7 +548,7 @@ void ExpectMinidumpContextMIPS(uint32_t expect_seed, EXPECT_EQ(observed->status, expected.status); EXPECT_EQ(observed->cause, expected.cause); - for (size_t index = 0; index < arraysize(expected.fpregs.fregs); ++index) { + for (size_t index = 0; index < base::size(expected.fpregs.fregs); ++index) { EXPECT_EQ(observed->fpregs.fregs[index]._fp_fregs, expected.fpregs.fregs[index]._fp_fregs); } @@ -568,7 +570,7 @@ void ExpectMinidumpContextMIPS64(uint32_t expect_seed, EXPECT_EQ(observed->context_flags, expected.context_flags); - for (size_t index = 0; index < arraysize(expected.regs); ++index) { + for (size_t index = 0; index < base::size(expected.regs); ++index) { EXPECT_EQ(observed->regs[index], expected.regs[index]); } @@ -579,7 +581,7 @@ void ExpectMinidumpContextMIPS64(uint32_t expect_seed, EXPECT_EQ(observed->status, expected.status); EXPECT_EQ(observed->cause, expected.cause); - for (size_t index = 0; index < arraysize(expected.fpregs.dregs); ++index) { + for (size_t index = 0; index < base::size(expected.fpregs.dregs); ++index) { EXPECT_EQ(observed->fpregs.dregs[index], expected.fpregs.dregs[index]); } EXPECT_EQ(observed->fpcsr, expected.fpcsr); diff --git a/minidump/test/minidump_writable_test_util.cc b/minidump/test/minidump_writable_test_util.cc index 1a832bf3..db07b366 100644 --- a/minidump/test/minidump_writable_test_util.cc +++ b/minidump/test/minidump_writable_test_util.cc @@ -210,17 +210,13 @@ struct MinidumpMemoryInfoListTraits { struct MinidumpModuleCrashpadInfoListTraits { using ListType = MinidumpModuleCrashpadInfoList; enum : size_t { kElementSize = sizeof(MinidumpModuleCrashpadInfoLink) }; - static size_t ElementCount(const ListType* list) { - return list->count; - } + static size_t ElementCount(const ListType* list) { return list->count; } }; struct MinidumpSimpleStringDictionaryListTraits { using ListType = MinidumpSimpleStringDictionary; enum : size_t { kElementSize = sizeof(MinidumpSimpleStringDictionaryEntry) }; - static size_t ElementCount(const ListType* list) { - return list->count; - } + static size_t ElementCount(const ListType* list) { return list->count; } }; struct MinidumpAnnotationListObjectsTraits { @@ -253,17 +249,19 @@ const typename T::ListType* MinidumpListAtLocationDescriptor( } // namespace template <> -const MINIDUMP_MEMORY_LIST* MinidumpWritableAtLocationDescriptor< - MINIDUMP_MEMORY_LIST>(const std::string& file_contents, - const MINIDUMP_LOCATION_DESCRIPTOR& location) { +const MINIDUMP_MEMORY_LIST* +MinidumpWritableAtLocationDescriptor( + const std::string& file_contents, + const MINIDUMP_LOCATION_DESCRIPTOR& location) { return MinidumpListAtLocationDescriptor( file_contents, location); } template <> -const MINIDUMP_MODULE_LIST* MinidumpWritableAtLocationDescriptor< - MINIDUMP_MODULE_LIST>(const std::string& file_contents, - const MINIDUMP_LOCATION_DESCRIPTOR& location) { +const MINIDUMP_MODULE_LIST* +MinidumpWritableAtLocationDescriptor( + const std::string& file_contents, + const MINIDUMP_LOCATION_DESCRIPTOR& location) { return MinidumpListAtLocationDescriptor( file_contents, location); } @@ -278,25 +276,28 @@ MinidumpWritableAtLocationDescriptor( } template <> -const MINIDUMP_THREAD_LIST* MinidumpWritableAtLocationDescriptor< - MINIDUMP_THREAD_LIST>(const std::string& file_contents, - const MINIDUMP_LOCATION_DESCRIPTOR& location) { +const MINIDUMP_THREAD_LIST* +MinidumpWritableAtLocationDescriptor( + const std::string& file_contents, + const MINIDUMP_LOCATION_DESCRIPTOR& location) { return MinidumpListAtLocationDescriptor( file_contents, location); } template <> -const MINIDUMP_HANDLE_DATA_STREAM* MinidumpWritableAtLocationDescriptor< - MINIDUMP_HANDLE_DATA_STREAM>(const std::string& file_contents, - const MINIDUMP_LOCATION_DESCRIPTOR& location) { +const MINIDUMP_HANDLE_DATA_STREAM* +MinidumpWritableAtLocationDescriptor( + const std::string& file_contents, + const MINIDUMP_LOCATION_DESCRIPTOR& location) { return MinidumpListAtLocationDescriptor( file_contents, location); } template <> -const MINIDUMP_MEMORY_INFO_LIST* MinidumpWritableAtLocationDescriptor< - MINIDUMP_MEMORY_INFO_LIST>(const std::string& file_contents, - const MINIDUMP_LOCATION_DESCRIPTOR& location) { +const MINIDUMP_MEMORY_INFO_LIST* +MinidumpWritableAtLocationDescriptor( + const std::string& file_contents, + const MINIDUMP_LOCATION_DESCRIPTOR& location) { return MinidumpListAtLocationDescriptor( file_contents, location); } @@ -357,28 +358,51 @@ const T* MinidumpCVPDBAtLocationDescriptor( } // namespace template <> -const CodeViewRecordPDB20* MinidumpWritableAtLocationDescriptor< - CodeViewRecordPDB20>(const std::string& file_contents, - const MINIDUMP_LOCATION_DESCRIPTOR& location) { +const CodeViewRecordPDB20* +MinidumpWritableAtLocationDescriptor( + const std::string& file_contents, + const MINIDUMP_LOCATION_DESCRIPTOR& location) { return MinidumpCVPDBAtLocationDescriptor(file_contents, location); } template <> -const CodeViewRecordPDB70* MinidumpWritableAtLocationDescriptor< - CodeViewRecordPDB70>(const std::string& file_contents, - const MINIDUMP_LOCATION_DESCRIPTOR& location) { +const CodeViewRecordPDB70* +MinidumpWritableAtLocationDescriptor( + const std::string& file_contents, + const MINIDUMP_LOCATION_DESCRIPTOR& location) { return MinidumpCVPDBAtLocationDescriptor(file_contents, location); } -TestUInt32MinidumpWritable::TestUInt32MinidumpWritable(uint32_t value) - : MinidumpWritable(), - value_(value) { +template <> +const CodeViewRecordBuildID* +MinidumpWritableAtLocationDescriptor( + const std::string& file_contents, + const MINIDUMP_LOCATION_DESCRIPTOR& location) { + const CodeViewRecordBuildID* cv = + reinterpret_cast( + MinidumpWritableAtLocationDescriptorInternal( + file_contents, + location, + offsetof(CodeViewRecordBuildID, build_id), + true)); + + if (!cv) { + return nullptr; + } + + if (cv->signature != CodeViewRecordBuildID::kSignature) { + return nullptr; + } + + return cv; } -TestUInt32MinidumpWritable::~TestUInt32MinidumpWritable() { -} +TestUInt32MinidumpWritable::TestUInt32MinidumpWritable(uint32_t value) + : MinidumpWritable(), value_(value) {} + +TestUInt32MinidumpWritable::~TestUInt32MinidumpWritable() {} size_t TestUInt32MinidumpWritable::SizeOfObject() { return sizeof(value_); diff --git a/minidump/test/minidump_writable_test_util.h b/minidump/test/minidump_writable_test_util.h index 5b176d2b..6c706fba 100644 --- a/minidump/test/minidump_writable_test_util.h +++ b/minidump/test/minidump_writable_test_util.h @@ -104,6 +104,7 @@ MINIDUMP_ALLOW_OVERSIZED_DATA(IMAGE_DEBUG_MISC); MINIDUMP_ALLOW_OVERSIZED_DATA(MINIDUMP_STRING); MINIDUMP_ALLOW_OVERSIZED_DATA(CodeViewRecordPDB20); MINIDUMP_ALLOW_OVERSIZED_DATA(CodeViewRecordPDB70); +MINIDUMP_ALLOW_OVERSIZED_DATA(CodeViewRecordBuildID); MINIDUMP_ALLOW_OVERSIZED_DATA(MinidumpUTF8String); // minidump_file_writer_test accesses its variable-sized test streams via a @@ -179,14 +180,16 @@ const MINIDUMP_HEADER* MinidumpWritableAtLocationDescriptor( const MINIDUMP_LOCATION_DESCRIPTOR& location); template <> -const MINIDUMP_MEMORY_LIST* MinidumpWritableAtLocationDescriptor< - MINIDUMP_MEMORY_LIST>(const std::string& file_contents, - const MINIDUMP_LOCATION_DESCRIPTOR& location); +const MINIDUMP_MEMORY_LIST* +MinidumpWritableAtLocationDescriptor( + const std::string& file_contents, + const MINIDUMP_LOCATION_DESCRIPTOR& location); template <> -const MINIDUMP_MODULE_LIST* MinidumpWritableAtLocationDescriptor< - MINIDUMP_MODULE_LIST>(const std::string& file_contents, - const MINIDUMP_LOCATION_DESCRIPTOR& location); +const MINIDUMP_MODULE_LIST* +MinidumpWritableAtLocationDescriptor( + const std::string& file_contents, + const MINIDUMP_LOCATION_DESCRIPTOR& location); template <> const MINIDUMP_UNLOADED_MODULE_LIST* @@ -195,29 +198,40 @@ MinidumpWritableAtLocationDescriptor( const MINIDUMP_LOCATION_DESCRIPTOR& location); template <> -const MINIDUMP_THREAD_LIST* MinidumpWritableAtLocationDescriptor< - MINIDUMP_THREAD_LIST>(const std::string& file_contents, - const MINIDUMP_LOCATION_DESCRIPTOR& location); +const MINIDUMP_THREAD_LIST* +MinidumpWritableAtLocationDescriptor( + const std::string& file_contents, + const MINIDUMP_LOCATION_DESCRIPTOR& location); template <> -const MINIDUMP_HANDLE_DATA_STREAM* MinidumpWritableAtLocationDescriptor< - MINIDUMP_HANDLE_DATA_STREAM>(const std::string& file_contents, - const MINIDUMP_LOCATION_DESCRIPTOR& location); +const MINIDUMP_HANDLE_DATA_STREAM* +MinidumpWritableAtLocationDescriptor( + const std::string& file_contents, + const MINIDUMP_LOCATION_DESCRIPTOR& location); template <> -const MINIDUMP_MEMORY_INFO_LIST* MinidumpWritableAtLocationDescriptor< - MINIDUMP_MEMORY_INFO_LIST>(const std::string& file_contents, - const MINIDUMP_LOCATION_DESCRIPTOR& location); +const MINIDUMP_MEMORY_INFO_LIST* +MinidumpWritableAtLocationDescriptor( + const std::string& file_contents, + const MINIDUMP_LOCATION_DESCRIPTOR& location); template <> -const CodeViewRecordPDB20* MinidumpWritableAtLocationDescriptor< - CodeViewRecordPDB20>(const std::string& file_contents, - const MINIDUMP_LOCATION_DESCRIPTOR& location); +const CodeViewRecordPDB20* +MinidumpWritableAtLocationDescriptor( + const std::string& file_contents, + const MINIDUMP_LOCATION_DESCRIPTOR& location); template <> -const CodeViewRecordPDB70* MinidumpWritableAtLocationDescriptor< - CodeViewRecordPDB70>(const std::string& file_contents, - const MINIDUMP_LOCATION_DESCRIPTOR& location); +const CodeViewRecordPDB70* +MinidumpWritableAtLocationDescriptor( + const std::string& file_contents, + const MINIDUMP_LOCATION_DESCRIPTOR& location); + +template <> +const CodeViewRecordBuildID* +MinidumpWritableAtLocationDescriptor( + const std::string& file_contents, + const MINIDUMP_LOCATION_DESCRIPTOR& location); template <> const MinidumpModuleCrashpadInfoList* diff --git a/snapshot/BUILD.gn b/snapshot/BUILD.gn index 134df8b1..fea22c2a 100644 --- a/snapshot/BUILD.gn +++ b/snapshot/BUILD.gn @@ -17,6 +17,9 @@ import("../build/crashpad_fuzzer_test.gni") if (crashpad_is_in_chromium) { import("//build/config/compiler/compiler.gni") + + # Prevent Chromium source assignment filters from being inherited. + set_sources_assignment_filter([]) } static_library("snapshot") { @@ -25,9 +28,6 @@ static_library("snapshot") { "annotation_snapshot.h", "capture_memory.cc", "capture_memory.h", - "cpu_architecture.h", - "cpu_context.cc", - "cpu_context.h", "crashpad_info_client_options.cc", "crashpad_info_client_options.h", "exception_snapshot.h", @@ -36,10 +36,17 @@ static_library("snapshot") { "memory_snapshot.cc", "memory_snapshot.h", "memory_snapshot_generic.h", + "minidump/exception_snapshot_minidump.cc", + "minidump/exception_snapshot_minidump.h", + "minidump/memory_snapshot_minidump.cc", + "minidump/memory_snapshot_minidump.h", "minidump/minidump_annotation_reader.cc", "minidump/minidump_annotation_reader.h", + "minidump/minidump_context_converter.cc", + "minidump/minidump_context_converter.h", "minidump/minidump_simple_string_dictionary_reader.cc", "minidump/minidump_simple_string_dictionary_reader.h", + "minidump/minidump_stream.h", "minidump/minidump_string_list_reader.cc", "minidump/minidump_string_list_reader.h", "minidump/minidump_string_reader.cc", @@ -48,6 +55,10 @@ static_library("snapshot") { "minidump/module_snapshot_minidump.h", "minidump/process_snapshot_minidump.cc", "minidump/process_snapshot_minidump.h", + "minidump/system_snapshot_minidump.cc", + "minidump/system_snapshot_minidump.h", + "minidump/thread_snapshot_minidump.cc", + "minidump/thread_snapshot_minidump.h", "module_snapshot.h", "process_snapshot.h", "snapshot_constants.h", @@ -86,16 +97,9 @@ static_library("snapshot") { "mac/process_snapshot_mac.h", "mac/process_types.cc", "mac/process_types.h", - "mac/process_types/all.proctype", - "mac/process_types/annotation.proctype", - "mac/process_types/crashpad_info.proctype", - "mac/process_types/crashreporterclient.proctype", "mac/process_types/custom.cc", - "mac/process_types/dyld_images.proctype", "mac/process_types/flavors.h", "mac/process_types/internal.h", - "mac/process_types/loader.proctype", - "mac/process_types/nlist.proctype", "mac/process_types/traits.h", "mac/system_snapshot_mac.cc", "mac/system_snapshot_mac.h", @@ -104,8 +108,26 @@ static_library("snapshot") { ] } + if (crashpad_is_ios) { + sources += [ + "ios/exception_snapshot_ios.cc", + "ios/exception_snapshot_ios.h", + "ios/memory_snapshot_ios.cc", + "ios/memory_snapshot_ios.h", + "ios/module_snapshot_ios.cc", + "ios/module_snapshot_ios.h", + "ios/process_snapshot_ios.cc", + "ios/process_snapshot_ios.h", + "ios/system_snapshot_ios.cc", + "ios/system_snapshot_ios.h", + "ios/thread_snapshot_ios.cc", + "ios/thread_snapshot_ios.h", + "mac/cpu_context_mac.cc", + "mac/cpu_context_mac.h", + ] + } + if (crashpad_is_linux || crashpad_is_android) { - set_sources_assignment_filter([]) sources += [ "linux/cpu_context_linux.cc", "linux/cpu_context_linux.h", @@ -135,10 +157,16 @@ static_library("snapshot") { ] } - if (crashpad_is_linux || crashpad_is_android || crashpad_is_fuchsia) { + if (crashpad_is_linux || crashpad_is_android || crashpad_is_fuchsia || + crashpad_is_win) { sources += [ "crashpad_types/crashpad_info_reader.cc", "crashpad_types/crashpad_info_reader.h", + ] + } + + if (crashpad_is_linux || crashpad_is_android || crashpad_is_fuchsia) { + sources += [ "crashpad_types/image_annotation_reader.cc", "crashpad_types/image_annotation_reader.h", "elf/elf_dynamic_array_reader.cc", @@ -162,8 +190,6 @@ static_library("snapshot") { "win/exception_snapshot_win.h", "win/memory_map_region_snapshot_win.cc", "win/memory_map_region_snapshot_win.h", - "win/memory_snapshot_win.cc", - "win/memory_snapshot_win.h", "win/module_snapshot_win.cc", "win/module_snapshot_win.h", "win/pe_image_annotations_reader.cc", @@ -193,6 +219,8 @@ static_library("snapshot") { "fuchsia/exception_snapshot_fuchsia.h", "fuchsia/memory_map_fuchsia.cc", "fuchsia/memory_map_fuchsia.h", + "fuchsia/memory_map_region_snapshot_fuchsia.cc", + "fuchsia/memory_map_region_snapshot_fuchsia.h", "fuchsia/process_reader_fuchsia.cc", "fuchsia/process_reader_fuchsia.h", "fuchsia/process_snapshot_fuchsia.cc", @@ -213,51 +241,58 @@ static_library("snapshot") { public_configs = [ "..:crashpad_config" ] + public_deps = [ ":context" ] + deps = [ "../client", "../compat", + "../minidump:format", + "../third_party/mini_chromium:base", + "../util", + ] + + if (crashpad_is_ios) { + deps -= [ "../client" ] + } + + if (crashpad_is_win) { + cflags = [ "/wd4201" ] # nonstandard extension used : nameless struct/union + libs = [ "powrprof.lib" ] + } + + configs += [ "..:disable_ubsan" ] +} + +# :context is the only part of snapshot that minidump may depend on. +static_library("context") { + sources = [ + "cpu_architecture.h", + "cpu_context.cc", + "cpu_context.h", + ] + + public_configs = [ "..:crashpad_config" ] + + deps = [ "../third_party/mini_chromium:base", "../util", ] if (crashpad_is_win) { cflags = [ "/wd4201" ] # nonstandard extension used : nameless struct/union - libs = [ "powrprof.lib" ] } } -if (crashpad_is_win) { - static_library("snapshot_api") { - sources = [ - "api/module_annotations_win.cc", - "api/module_annotations_win.h", - ] - - public_configs = [ "..:crashpad_config" ] - - cflags = [ "/wd4201" ] +if (crashpad_is_linux) { + crashpad_fuzzer_test("elf_image_reader_fuzzer") { + sources = [ "elf/elf_image_reader_fuzzer.cc" ] deps = [ ":snapshot", - "../compat", "../third_party/mini_chromium:base", - "../util", ] + seed_corpus = "elf/elf_image_reader_fuzzer_corpus" } -} else { - group("snapshot_api") { - } -} - -fuzzer_test("elf_image_reader_fuzzer") { - sources = [ - "elf/elf_image_reader_fuzzer.cc", - ] - - deps = [ - ":snapshot", - "../third_party/mini_chromium:base", - ] } static_library("test_support") { @@ -284,9 +319,7 @@ static_library("test_support") { public_configs = [ "..:crashpad_config" ] - public_deps = [ - ":snapshot", - ] + public_deps = [ ":snapshot" ] deps = [ "../compat", @@ -305,9 +338,7 @@ config("snapshot_test_link") { # There’s no way to make the link depend on this file. “inputs” doesn’t have # the intended effect in a config. https://crbug.com/781858, # https://crbug.com/796187. - inputs = [ - "elf/test_exported_symbols.sym", - ] + inputs = [ "elf/test_exported_symbols.sym" ] ldflags = [ "-Wl,--dynamic-list," + rebase_path(inputs[0], root_build_dir) ] } } @@ -342,27 +373,29 @@ source_set("snapshot_test") { "sanitized/process_snapshot_sanitized_test.cc", "sanitized/sanitization_information_test.cc", ] - } else { + } else if (!crashpad_is_ios) { sources += [ "crashpad_info_client_options_test.cc" ] } + if (crashpad_is_linux || crashpad_is_android || crashpad_is_fuchsia || + crashpad_is_win) { + sources += [ "crashpad_types/crashpad_info_reader_test.cc" ] + } + if (crashpad_is_linux || crashpad_is_android || crashpad_is_fuchsia) { sources += [ - "crashpad_types/crashpad_info_reader_test.cc", "crashpad_types/image_annotation_reader_test.cc", "elf/elf_image_reader_test.cc", "elf/elf_image_reader_test_note.S", - "elf/test_exported_symbols.sym", ] } if (crashpad_is_win) { sources += [ - "api/module_annotations_win_test.cc", "win/cpu_context_win_test.cc", "win/exception_snapshot_win_test.cc", "win/extra_memory_ranges_test.cc", - "win/pe_image_annotations_reader_test.cc", + "win/module_snapshot_win_test.cc", "win/pe_image_reader_test.cc", "win/process_reader_win_test.cc", "win/process_snapshot_win_test.cc", @@ -377,7 +410,10 @@ source_set("snapshot_test") { } if (crashpad_is_fuchsia) { - sources += [ "fuchsia/process_reader_fuchsia_test.cc" ] + sources += [ + "fuchsia/process_reader_fuchsia_test.cc", + "fuchsia/process_snapshot_fuchsia_test.cc", + ] } # public_configs isn’t quite right. snapshot_test_link sets ldflags, and @@ -387,10 +423,10 @@ source_set("snapshot_test") { public_configs = [ ":snapshot_test_link" ] deps = [ - ":snapshot_api", ":test_support", "../client", "../compat", + "../minidump:format", "../test", "../third_party/gtest:gtest", "../third_party/mini_chromium:base", @@ -437,9 +473,7 @@ source_set("snapshot_test") { crashpad_loadable_module("crashpad_snapshot_test_module") { testonly = true - sources = [ - "crashpad_info_client_options_test_module.cc", - ] + sources = [ "crashpad_info_client_options_test_module.cc" ] deps = [ "../client", "../third_party/mini_chromium:base", @@ -448,9 +482,7 @@ crashpad_loadable_module("crashpad_snapshot_test_module") { crashpad_loadable_module("crashpad_snapshot_test_module_large") { testonly = true - sources = [ - "crashpad_info_size_test_module.cc", - ] + sources = [ "crashpad_info_size_test_module.cc" ] deps = [] if (crashpad_is_linux || crashpad_is_android || crashpad_is_fuchsia) { @@ -464,9 +496,7 @@ crashpad_loadable_module("crashpad_snapshot_test_module_large") { crashpad_loadable_module("crashpad_snapshot_test_module_small") { testonly = true - sources = [ - "crashpad_info_size_test_module.cc", - ] + sources = [ "crashpad_info_size_test_module.cc" ] deps = [] if (crashpad_is_linux || crashpad_is_android || crashpad_is_fuchsia) { @@ -482,9 +512,7 @@ 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", - ] + sources = [ "hash_types_test.cc" ] # This makes `ld` emit both .hash and .gnu.hash sections. ldflags = [ "-Wl,--hash-style=both" ] @@ -501,18 +529,14 @@ if (crashpad_is_mac) { crashpad_executable("crashpad_snapshot_test_no_op") { testonly = true - sources = [ - "mac/mach_o_image_annotations_reader_test_no_op.cc", - ] + sources = [ "mac/mach_o_image_annotations_reader_test_no_op.cc" ] } } if (crashpad_is_win) { crashpad_executable("crashpad_snapshot_test_annotations") { testonly = true - sources = [ - "win/crashpad_snapshot_test_annotations.cc", - ] + sources = [ "win/crashpad_snapshot_test_annotations.cc" ] deps = [ "../client", "../compat", @@ -522,9 +546,7 @@ if (crashpad_is_win) { crashpad_executable("crashpad_snapshot_test_crashing_child") { testonly = true - sources = [ - "win/crashpad_snapshot_test_crashing_child.cc", - ] + sources = [ "win/crashpad_snapshot_test_crashing_child.cc" ] deps = [ "../client", "../compat", @@ -535,9 +557,7 @@ if (crashpad_is_win) { crashpad_executable("crashpad_snapshot_test_dump_without_crashing") { testonly = true - sources = [ - "win/crashpad_snapshot_test_dump_without_crashing.cc", - ] + sources = [ "win/crashpad_snapshot_test_dump_without_crashing.cc" ] deps = [ "../client", "../compat", @@ -548,9 +568,7 @@ if (crashpad_is_win) { crashpad_executable("crashpad_snapshot_test_extra_memory_ranges") { testonly = true - sources = [ - "win/crashpad_snapshot_test_extra_memory_ranges.cc", - ] + sources = [ "win/crashpad_snapshot_test_extra_memory_ranges.cc" ] deps = [ "../client", "../compat", @@ -560,9 +578,7 @@ if (crashpad_is_win) { crashpad_executable("crashpad_snapshot_test_image_reader") { testonly = true - sources = [ - "win/crashpad_snapshot_test_image_reader.cc", - ] + sources = [ "win/crashpad_snapshot_test_image_reader.cc" ] deps = [ "../client", "../compat", @@ -581,9 +597,7 @@ if (crashpad_is_win) { crashpad_loadable_module("crashpad_snapshot_test_image_reader_module") { testonly = true - sources = [ - "win/crashpad_snapshot_test_image_reader_module.cc", - ] + sources = [ "win/crashpad_snapshot_test_image_reader_module.cc" ] deps = [ "../client", "../third_party/mini_chromium:base", diff --git a/snapshot/api/module_annotations_win.cc b/snapshot/api/module_annotations_win.cc deleted file mode 100644 index fd468703..00000000 --- a/snapshot/api/module_annotations_win.cc +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2016 The Crashpad Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "snapshot/api/module_annotations_win.h" - -#include "snapshot/win/pe_image_annotations_reader.h" -#include "snapshot/win/pe_image_reader.h" -#include "snapshot/win/process_reader_win.h" -#include "util/misc/from_pointer_cast.h" -#include "util/win/get_module_information.h" - -namespace crashpad { - -bool ReadModuleAnnotations(HANDLE process, - HMODULE module, - std::map* annotations) { - ProcessReaderWin process_reader; - if (!process_reader.Initialize(process, ProcessSuspensionState::kRunning)) - return false; - - MODULEINFO module_info; - if (!CrashpadGetModuleInformation( - process, module, &module_info, sizeof(module_info))) { - PLOG(ERROR) << "CrashpadGetModuleInformation"; - return false; - } - - PEImageReader image_reader; - if (!image_reader.Initialize( - &process_reader, - FromPointerCast(module_info.lpBaseOfDll), - module_info.SizeOfImage, - "")) - return false; - - PEImageAnnotationsReader annotations_reader( - &process_reader, &image_reader, L""); - - *annotations = annotations_reader.SimpleMap(); - return true; -} - -} // namespace crashpad diff --git a/snapshot/api/module_annotations_win.h b/snapshot/api/module_annotations_win.h deleted file mode 100644 index e0240310..00000000 --- a/snapshot/api/module_annotations_win.h +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2016 The Crashpad Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef CRASHPAD_SNAPSHOT_API_MODULE_ANNOTATIONS_WIN_H_ -#define CRASHPAD_SNAPSHOT_API_MODULE_ANNOTATIONS_WIN_H_ - -#include - -#include -#include - -namespace crashpad { - -//! \brief Reads the module annotations from another process. -//! -//! \param[in] process The handle to the process that hosts the \a module. -//! Requires PROCESS_QUERY_INFORMATION and PROCESS_VM_READ accesses. -//! \param[in] module The handle to the module from which the \a annotations -//! will be read. This module should be loaded in the target process. -//! \param[out] annotations The map that will be filled with the annotations. -//! Remains unchanged if the function returns 'false'. -//! -//! \return `true` if the annotations could be read succesfully, even if the -//! module doesn't contain any annotations. -bool ReadModuleAnnotations(HANDLE process, - HMODULE module, - std::map* annotations); - -} // namespace crashpad - -#endif // CRASHPAD_SNAPSHOT_API_MODULE_ANNOTATIONS_WIN_H_ diff --git a/snapshot/api/module_annotations_win_test.cc b/snapshot/api/module_annotations_win_test.cc deleted file mode 100644 index ecfa4659..00000000 --- a/snapshot/api/module_annotations_win_test.cc +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2016 The Crashpad Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "snapshot/api/module_annotations_win.h" - -#include "client/crashpad_info.h" -#include "gtest/gtest.h" -#include "test/win/win_multiprocess.h" -#include "util/file/file_io.h" - -namespace crashpad { -namespace test { -namespace { - -class ModuleAnnotationsMultiprocessTest final : public WinMultiprocess { - private: - void WinMultiprocessParent() override { - // Read the child executable module. - HMODULE module = nullptr; - CheckedReadFileExactly(ReadPipeHandle(), &module, sizeof(module)); - - // Reopen the child process with necessary access. - HANDLE process_handle = - OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, - FALSE, - GetProcessId(ChildProcess())); - EXPECT_TRUE(process_handle); - - // Read the module annotations in the child process and verify them. - std::map annotations; - ASSERT_TRUE(ReadModuleAnnotations(process_handle, module, &annotations)); - - EXPECT_GE(annotations.size(), 3u); - EXPECT_EQ(annotations["#APITEST# key"], "value"); - EXPECT_EQ(annotations["#APITEST# x"], "y"); - EXPECT_EQ(annotations["#APITEST# empty_value"], ""); - - // Signal the child process to terminate. - char c = ' '; - CheckedWriteFile(WritePipeHandle(), &c, sizeof(c)); - } - - void WinMultiprocessChild() override { - // Set some test annotations. - crashpad::CrashpadInfo* crashpad_info = - crashpad::CrashpadInfo::GetCrashpadInfo(); - - crashpad::SimpleStringDictionary* simple_annotations = - new crashpad::SimpleStringDictionary(); - simple_annotations->SetKeyValue("#APITEST# key", "value"); - simple_annotations->SetKeyValue("#APITEST# x", "y"); - simple_annotations->SetKeyValue("#APITEST# empty_value", ""); - - crashpad_info->set_simple_annotations(simple_annotations); - - // Send the executable module. - HMODULE module = GetModuleHandle(nullptr); - CheckedWriteFile(WritePipeHandle(), &module, sizeof(module)); - - // Wait until a signal from the parent process to terminate. - char c; - CheckedReadFileExactly(ReadPipeHandle(), &c, sizeof(c)); - } -}; - -TEST(ModuleAnnotationsWin, ReadAnnotations) { - WinMultiprocess::Run(); -} - -} // namespace -} // namespace test -} // namespace crashpad diff --git a/snapshot/capture_memory.cc b/snapshot/capture_memory.cc index 98f400e8..a51626cc 100644 --- a/snapshot/capture_memory.cc +++ b/snapshot/capture_memory.cc @@ -19,6 +19,7 @@ #include #include +#include "base/stl_util.h" #include "snapshot/memory_snapshot.h" namespace crashpad { @@ -97,17 +98,17 @@ void CaptureMemory::PointedToByContext(const CPUContext& context, #elif defined(ARCH_CPU_ARM_FAMILY) if (context.architecture == kCPUArchitectureARM64) { MaybeCaptureMemoryAround(delegate, context.arm64->pc); - for (size_t i = 0; i < arraysize(context.arm64->regs); ++i) { + for (size_t i = 0; i < base::size(context.arm64->regs); ++i) { MaybeCaptureMemoryAround(delegate, context.arm64->regs[i]); } } else { MaybeCaptureMemoryAround(delegate, context.arm->pc); - for (size_t i = 0; i < arraysize(context.arm->regs); ++i) { + for (size_t i = 0; i < base::size(context.arm->regs); ++i) { MaybeCaptureMemoryAround(delegate, context.arm->regs[i]); } } #elif defined(ARCH_CPU_MIPS_FAMILY) - for (size_t i = 0; i < arraysize(context.mipsel->regs); ++i) { + for (size_t i = 0; i < base::size(context.mipsel->regs); ++i) { MaybeCaptureMemoryAround(delegate, context.mipsel->regs[i]); } #else diff --git a/snapshot/cpu_context.cc b/snapshot/cpu_context.cc index 4d7c1e5a..6fb8d7e7 100644 --- a/snapshot/cpu_context.cc +++ b/snapshot/cpu_context.cc @@ -18,7 +18,8 @@ #include #include "base/logging.h" -#include "base/macros.h" +#include "base/stl_util.h" +#include "util/misc/arraysize.h" #include "util/misc/implicit_cast.h" namespace crashpad { @@ -55,9 +56,9 @@ void CPUContextX86::FxsaveToFsave(const Fxsave& fxsave, Fsave* fsave) { fsave->fpu_dp = fxsave.fpu_dp; fsave->fpu_ds = fxsave.fpu_ds; fsave->reserved_4 = 0; - static_assert(arraysize(fsave->st) == arraysize(fxsave.st_mm), + static_assert(ArraySize(fsave->st) == ArraySize(fxsave.st_mm), "FPU stack registers must be equivalent"); - for (size_t index = 0; index < arraysize(fsave->st); ++index) { + for (size_t index = 0; index < base::size(fsave->st); ++index) { memcpy(fsave->st[index], fxsave.st_mm[index].st, sizeof(fsave->st[index])); } } @@ -77,9 +78,9 @@ void CPUContextX86::FsaveToFxsave(const Fsave& fsave, Fxsave* fxsave) { fxsave->reserved_3 = 0; fxsave->mxcsr = 0; fxsave->mxcsr_mask = 0; - static_assert(arraysize(fxsave->st_mm) == arraysize(fsave.st), + static_assert(ArraySize(fxsave->st_mm) == ArraySize(fsave.st), "FPU stack registers must be equivalent"); - for (size_t index = 0; index < arraysize(fsave.st); ++index) { + for (size_t index = 0; index < base::size(fsave.st); ++index) { memcpy(fxsave->st_mm[index].st, fsave.st[index], sizeof(fsave.st[index])); memset(fxsave->st_mm[index].st_reserved, 0, diff --git a/snapshot/cpu_context.h b/snapshot/cpu_context.h index 4dde9436..fb23c467 100644 --- a/snapshot/cpu_context.h +++ b/snapshot/cpu_context.h @@ -299,7 +299,7 @@ struct CPUContextARM64 { uint64_t regs[31]; uint64_t sp; uint64_t pc; - uint64_t pstate; + uint32_t spsr; uint128_struct fpsimd[32]; uint32_t fpsr; diff --git a/snapshot/cpu_context_test.cc b/snapshot/cpu_context_test.cc index 706f2fe5..109510d3 100644 --- a/snapshot/cpu_context_test.cc +++ b/snapshot/cpu_context_test.cc @@ -18,7 +18,7 @@ #include #include -#include "base/macros.h" +#include "base/stl_util.h" #include "gtest/gtest.h" #include "test/hex_string.h" @@ -124,7 +124,7 @@ TEST(CPUContextX86, FxsaveToFsave) { &fxsave.st_mm[6].st, kExponentAllZero, false, kFractionAllZero); SetX87Register( &fxsave.st_mm[7].st, kExponentNormal, true, kFractionNormal); // valid - for (size_t index = 0; index < arraysize(fxsave.st_mm); ++index) { + for (size_t index = 0; index < base::size(fxsave.st_mm); ++index) { memset(&fxsave.st_mm[index].st_reserved, 0x5a, sizeof(fxsave.st_mm[index].st_reserved)); @@ -148,10 +148,10 @@ TEST(CPUContextX86, FxsaveToFsave) { EXPECT_EQ(fsave.fpu_dp, fxsave.fpu_dp); EXPECT_EQ(fsave.fpu_ds, fxsave.fpu_ds); EXPECT_EQ(fsave.reserved_4, 0); - for (size_t index = 0; index < arraysize(fsave.st); ++index) { - EXPECT_EQ(BytesToHexString(fsave.st[index], arraysize(fsave.st[index])), + for (size_t index = 0; index < base::size(fsave.st); ++index) { + EXPECT_EQ(BytesToHexString(fsave.st[index], base::size(fsave.st[index])), BytesToHexString(fxsave.st_mm[index].st, - arraysize(fxsave.st_mm[index].st))) + base::size(fxsave.st_mm[index].st))) << "index " << index; } } @@ -204,14 +204,14 @@ TEST(CPUContextX86, FsaveToFxsave) { EXPECT_EQ(fxsave.reserved_3, 0); EXPECT_EQ(fxsave.mxcsr, 0u); EXPECT_EQ(fxsave.mxcsr_mask, 0u); - for (size_t index = 0; index < arraysize(fxsave.st_mm); ++index) { + for (size_t index = 0; index < base::size(fxsave.st_mm); ++index) { EXPECT_EQ(BytesToHexString(fxsave.st_mm[index].st, - arraysize(fxsave.st_mm[index].st)), - BytesToHexString(fsave.st[index], arraysize(fsave.st[index]))) + base::size(fxsave.st_mm[index].st)), + BytesToHexString(fsave.st[index], base::size(fsave.st[index]))) << "index " << index; EXPECT_EQ(BytesToHexString(fxsave.st_mm[index].st_reserved, - arraysize(fxsave.st_mm[index].st_reserved)), - std::string(arraysize(fxsave.st_mm[index].st_reserved) * 2, '0')) + base::size(fxsave.st_mm[index].st_reserved)), + std::string(base::size(fxsave.st_mm[index].st_reserved) * 2, '0')) << "index " << index; } size_t unused_len = sizeof(fxsave) - offsetof(decltype(fxsave), xmm); @@ -318,7 +318,7 @@ TEST(CPUContextX86, FxsaveToFsaveTagWord) { // In this set, everything is valid. fsw = 0 << 11; // top = 0: logical 0-7 maps to physical 0-7 fxsave_tag = 0xff; // nothing empty - for (size_t index = 0; index < arraysize(st_mm); ++index) { + for (size_t index = 0; index < base::size(st_mm); ++index) { SetX87OrMMXRegister(&st_mm[index], kExponentNormal, true, kFractionAllZero); } EXPECT_EQ(CPUContextX86::FxsaveToFsaveTagWord(fsw, fxsave_tag, st_mm), 0); diff --git a/snapshot/crashpad_info_client_options.cc b/snapshot/crashpad_info_client_options.cc index 4a89d84a..3a88a4c0 100644 --- a/snapshot/crashpad_info_client_options.cc +++ b/snapshot/crashpad_info_client_options.cc @@ -15,7 +15,6 @@ #include "snapshot/crashpad_info_client_options.h" #include "base/logging.h" -#include "client/crashpad_info.h" namespace crashpad { @@ -39,7 +38,8 @@ TriState CrashpadInfoClientOptions::TriStateFromCrashpadInfo( CrashpadInfoClientOptions::CrashpadInfoClientOptions() : crashpad_handler_behavior(TriState::kUnset), system_crash_reporter_forwarding(TriState::kUnset), - gather_indirectly_referenced_memory(TriState::kUnset) { + gather_indirectly_referenced_memory(TriState::kUnset), + indirectly_referenced_memory_cap(0) { } } // namespace crashpad diff --git a/snapshot/crashpad_info_client_options_test.cc b/snapshot/crashpad_info_client_options_test.cc index 1fe38acf..1c5759eb 100644 --- a/snapshot/crashpad_info_client_options_test.cc +++ b/snapshot/crashpad_info_client_options_test.cc @@ -32,7 +32,7 @@ #include #include "snapshot/win/process_snapshot_win.h" #elif defined(OS_FUCHSIA) -#include +#include #include "snapshot/fuchsia/process_snapshot_fuchsia.h" #endif @@ -86,7 +86,7 @@ CrashpadInfoClientOptions SelfProcessSnapshotAndGetCrashpadOptions() { GetCurrentProcess(), ProcessSuspensionState::kRunning, 0, 0)); #elif defined(OS_FUCHSIA) ProcessSnapshotFuchsia process_snapshot; - EXPECT_TRUE(process_snapshot.Initialize(zx_process_self())); + EXPECT_TRUE(process_snapshot.Initialize(*zx::process::self())); #else #error Port. #endif // OS_MACOSX @@ -326,10 +326,10 @@ TEST_P(CrashpadInfoSizes_ClientOptions, DifferentlySizedStruct) { } } -INSTANTIATE_TEST_CASE_P(CrashpadInfoSizes_ClientOptions, - CrashpadInfoSizes_ClientOptions, - testing::Values(FILE_PATH_LITERAL("small"), - FILE_PATH_LITERAL("large"))); +INSTANTIATE_TEST_SUITE_P(CrashpadInfoSizes_ClientOptions, + CrashpadInfoSizes_ClientOptions, + testing::Values(FILE_PATH_LITERAL("small"), + FILE_PATH_LITERAL("large"))); } // namespace } // namespace test diff --git a/snapshot/crashpad_info_size_test_note.S b/snapshot/crashpad_info_size_test_note.S index 96b996db..16b5d499 100644 --- a/snapshot/crashpad_info_size_test_note.S +++ b/snapshot/crashpad_info_size_test_note.S @@ -26,9 +26,11 @@ #define NOTE_ALIGN 4 // This section must be "a"llocated so that it appears in the final binary at - // runtime, and "w"ritable so that the relocation to TEST_CRASHPAD_INFO_SYMBOL - // can be performed. - .section .note.crashpad.info,"aw",%note + // runtime. The reference to TEST_CRASHPAD_INFO_SYMBOL uses an offset relative + // to this note to avoid making this note writable, which triggers a bug in + // GNU ld, or adding text relocations which require the target system to allow + // making text segments writable. https://crbug.com/crashpad/260. + .section .note.crashpad.info,"a",%note .balign NOTE_ALIGN .type info_size_test_note, %object info_size_test_note: @@ -41,15 +43,9 @@ name_end: .balign NOTE_ALIGN desc: #if defined(__LP64__) - .quad TEST_CRASHPAD_INFO_SYMBOL + .quad TEST_CRASHPAD_INFO_SYMBOL - desc #else -#if defined(__LITTLE_ENDIAN__) - .long TEST_CRASHPAD_INFO_SYMBOL - .long 0 -#else - .long 0 - .long TEST_CRASHPAD_INFO_SYMBOL -#endif // __LITTLE_ENDIAN__ + .long TEST_CRASHPAD_INFO_SYMBOL - desc #endif // __LP64__ desc_end: .size info_size_test_note, .-info_size_test_note diff --git a/snapshot/crashpad_types/crashpad_info_reader.cc b/snapshot/crashpad_types/crashpad_info_reader.cc index dfc438fc..07a9b48e 100644 --- a/snapshot/crashpad_types/crashpad_info_reader.cc +++ b/snapshot/crashpad_types/crashpad_info_reader.cc @@ -66,9 +66,8 @@ class CrashpadInfoReader::InfoContainerSpecific : public InfoContainer { return false; } - if (!memory->Read(address, - std::min(VMSize{info.size}, VMSize{sizeof(info)}), - &info)) { + if (!memory->Read( + address, std::min(info.size, sizeof(info)), &info)) { return false; } @@ -116,7 +115,7 @@ class CrashpadInfoReader::InfoContainerSpecific : public InfoContainer { #define NATIVE_TRAITS Traits32 #endif static_assert(!std::is_same::value || - sizeof(info) == sizeof(CrashpadInfo), + sizeof(decltype(info)) == sizeof(CrashpadInfo), "CrashpadInfo size mismtach"); #undef NATIVE_TRAITS }; @@ -161,29 +160,29 @@ bool CrashpadInfoReader::Initialize(const ProcessMemoryRange* memory, return GET_MEMBER(member); \ } -DEFINE_GETTER(TriState, CrashpadHandlerBehavior, crashpad_handler_behavior); +DEFINE_GETTER(TriState, CrashpadHandlerBehavior, crashpad_handler_behavior) DEFINE_GETTER(TriState, SystemCrashReporterForwarding, - system_crash_reporter_forwarding); + system_crash_reporter_forwarding) DEFINE_GETTER(TriState, GatherIndirectlyReferencedMemory, - gather_indirectly_referenced_memory); + gather_indirectly_referenced_memory) DEFINE_GETTER(uint32_t, IndirectlyReferencedMemoryCap, - indirectly_referenced_memory_cap); + indirectly_referenced_memory_cap) -DEFINE_GETTER(VMAddress, ExtraMemoryRanges, extra_memory_ranges); +DEFINE_GETTER(VMAddress, ExtraMemoryRanges, extra_memory_ranges) -DEFINE_GETTER(VMAddress, SimpleAnnotations, simple_annotations); +DEFINE_GETTER(VMAddress, SimpleAnnotations, simple_annotations) -DEFINE_GETTER(VMAddress, AnnotationsList, annotations_list); +DEFINE_GETTER(VMAddress, AnnotationsList, annotations_list) DEFINE_GETTER(VMAddress, UserDataMinidumpStreamHead, - user_data_minidump_stream_head); + user_data_minidump_stream_head) #undef DEFINE_GETTER #undef GET_MEMBER diff --git a/snapshot/crashpad_types/crashpad_info_reader_test.cc b/snapshot/crashpad_types/crashpad_info_reader_test.cc index 87bafc68..ebc6a4d3 100644 --- a/snapshot/crashpad_types/crashpad_info_reader_test.cc +++ b/snapshot/crashpad_types/crashpad_info_reader_test.cc @@ -15,7 +15,6 @@ #include "snapshot/crashpad_types/crashpad_info_reader.h" #include -#include #include @@ -31,10 +30,6 @@ #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 { diff --git a/snapshot/elf/elf_dynamic_array_reader.cc b/snapshot/elf/elf_dynamic_array_reader.cc index 9a44e12e..a39712ac 100644 --- a/snapshot/elf/elf_dynamic_array_reader.cc +++ b/snapshot/elf/elf_dynamic_array_reader.cc @@ -42,9 +42,6 @@ bool Read(const ProcessMemoryRange& memory, switch (entry.d_tag) { case DT_NULL: values->swap(local_values); - if (size != 0) { - LOG(WARNING) << size << " trailing bytes not read"; - } return true; case DT_NEEDED: // Skip these entries for now. diff --git a/snapshot/elf/elf_image_reader.cc b/snapshot/elf/elf_image_reader.cc index bbf9b54e..7f6d7c7e 100644 --- a/snapshot/elf/elf_image_reader.cc +++ b/snapshot/elf/elf_image_reader.cc @@ -22,6 +22,7 @@ #include #include "base/logging.h" +#include "base/numerics/safe_math.h" #include "build/build_config.h" #include "util/numeric/checked_vm_address_range.h" @@ -191,7 +192,8 @@ ElfImageReader::NoteReader::~NoteReader() = default; ElfImageReader::NoteReader::Result ElfImageReader::NoteReader::NextNote( std::string* name, NoteType* type, - std::string* desc) { + std::string* desc, + VMAddress* desc_address) { if (!is_valid_) { LOG(ERROR) << "invalid note reader"; return Result::kError; @@ -215,8 +217,9 @@ ElfImageReader::NoteReader::Result ElfImageReader::NoteReader::NextNote( } retry_ = false; - result = range_->Is64Bit() ? ReadNote(name, type, desc) - : ReadNote(name, type, desc); + result = range_->Is64Bit() + ? ReadNote(name, type, desc, desc_address) + : ReadNote(name, type, desc, desc_address); } while (retry_); if (result == Result::kSuccess) { @@ -226,10 +229,19 @@ ElfImageReader::NoteReader::Result ElfImageReader::NoteReader::NextNote( return Result::kError; } +namespace { + +// The maximum size the user can specify for maximum note size. Clamping this +// ensures that buffer allocations cannot be wildly large. It is not expected +// that a note would be larger than ~1k in normal usage. +constexpr size_t kMaxMaxNoteSize = 16384; + +} // namespace + ElfImageReader::NoteReader::NoteReader(const ElfImageReader* elf_reader, const ProcessMemoryRange* range, const ProgramHeaderTable* phdr_table, - ssize_t max_note_size, + size_t max_note_size, const std::string& name_filter, NoteType type_filter, bool use_filter) @@ -240,18 +252,21 @@ ElfImageReader::NoteReader::NoteReader(const ElfImageReader* elf_reader, phdr_table_(phdr_table), segment_range_(), phdr_index_(0), - max_note_size_(max_note_size), + max_note_size_(std::min(kMaxMaxNoteSize, max_note_size)), name_filter_(name_filter), type_filter_(type_filter), use_filter_(use_filter), is_valid_(true), - retry_(false) {} + retry_(false) { + DCHECK_LT(max_note_size, kMaxMaxNoteSize); +} template ElfImageReader::NoteReader::Result ElfImageReader::NoteReader::ReadNote( std::string* name, NoteType* type, - std::string* desc) { + std::string* desc, + VMAddress* desc_address) { static_assert(sizeof(*type) >= sizeof(NhdrType::n_namesz), "Note field size mismatch"); DCHECK_LT(current_address_, segment_end_address_); @@ -263,10 +278,24 @@ ElfImageReader::NoteReader::Result ElfImageReader::NoteReader::ReadNote( current_address_ += sizeof(note_info); constexpr size_t align = sizeof(note_info.n_namesz); -#define PAD(x) (((x) + align - 1) & ~(align - 1)) - size_t padded_namesz = PAD(note_info.n_namesz); - size_t padded_descsz = PAD(note_info.n_descsz); - size_t note_size = padded_namesz + padded_descsz; + +#define CHECKED_PAD(x, into) \ + base::CheckAnd(base::CheckAdd(x, align - 1), ~(align - 1)) \ + .AssignIfValid(&into) + + size_t padded_namesz; + if (!CHECKED_PAD(note_info.n_namesz, padded_namesz)) { + return Result::kError; + } + size_t padded_descsz; + if (!CHECKED_PAD(note_info.n_descsz, padded_descsz)) { + return Result::kError; + } + + size_t note_size; + if (!base::CheckAdd(padded_namesz, padded_descsz).AssignIfValid(¬e_size)) { + return Result::kError; + } // Notes typically have 4-byte alignment. However, .note.android.ident may // inadvertently use 2-byte alignment. @@ -275,11 +304,21 @@ ElfImageReader::NoteReader::Result ElfImageReader::NoteReader::ReadNote( // 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_)) { + VMAddress end_of_note_candidate; + if (!base::CheckAdd(current_address_, note_size) + .AssignIfValid(&end_of_note_candidate)) { + return Result::kError; + } + VMAddress end_of_note; + if (!CHECKED_PAD(end_of_note_candidate, end_of_note)) { + return Result::kError; + } + end_of_note = std::min(end_of_note, segment_end_address_); + +#undef CHECKED_PAD + + if (note_size > max_note_size_) { current_address_ = end_of_note; retry_ = true; return Result::kError; @@ -317,6 +356,7 @@ ElfImageReader::NoteReader::Result ElfImageReader::NoteReader::ReadNote( current_address_, note_info.n_descsz, &local_desc[0])) { return Result::kError; } + *desc_address = current_address_; current_address_ = end_of_note; @@ -467,6 +507,20 @@ uint16_t ElfImageReader::FileType() const { return memory_.Is64Bit() ? header_64_.e_type : header_32_.e_type; } +bool ElfImageReader::SoName(std::string* name) { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + if (!InitializeDynamicArray()) { + return false; + } + + VMSize offset; + if (!dynamic_array_->GetValue(DT_SONAME, true, &offset)) { + return false; + } + + return ReadDynamicStringTableAtOffset(offset, name); +} + bool ElfImageReader::GetDynamicSymbol(const std::string& name, VMAddress* address, VMSize* size) { @@ -772,7 +826,7 @@ bool ElfImageReader::GetNumberOfSymbolEntriesFromDtGnuHash( } std::unique_ptr ElfImageReader::Notes( - ssize_t max_note_size) { + size_t max_note_size) { return std::make_unique( this, &memory_, program_headers_.get(), max_note_size); } @@ -780,7 +834,7 @@ std::unique_ptr ElfImageReader::Notes( std::unique_ptr ElfImageReader::NotesWithNameAndType(const std::string& name, NoteReader::NoteType type, - ssize_t max_note_size) { + size_t max_note_size) { return std::make_unique( this, &memory_, program_headers_.get(), max_note_size, name, type, true); } diff --git a/snapshot/elf/elf_image_reader.h b/snapshot/elf/elf_image_reader.h index 29f8b1eb..ab1799f3 100644 --- a/snapshot/elf/elf_image_reader.h +++ b/snapshot/elf/elf_image_reader.h @@ -70,15 +70,20 @@ class ElfImageReader { //! \param[out] name The name of the note owner, if not `nullptr`. //! \param[out] type A type for the note, if not `nullptr`. //! \param[out] desc The note descriptor. - //! \return a #Result value. \a name, \a type, and \a desc are only valid if - //! this method returns Result::kSuccess. - Result NextNote(std::string* name, NoteType* type, std::string* desc); + //! \param[out] desc_addr The address in the remote process' address space + //! \a desc was read from. + //! \return a #Result value. \a name, \a type, \a desc, and \a desc_addr are + //! only valid if this method returns Result::kSuccess. + Result NextNote(std::string* name, + NoteType* type, + std::string* desc, + VMAddress* desc_addr); // private NoteReader(const ElfImageReader* elf_reader_, const ProcessMemoryRange* range, const ProgramHeaderTable* phdr_table, - ssize_t max_note_size, + size_t max_note_size, const std::string& name_filter = std::string(), NoteType type_filter = 0, bool use_filter = false); @@ -88,7 +93,10 @@ class ElfImageReader { // 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); + Result ReadNote(std::string* name, + NoteType* type, + std::string* desc, + VMAddress* desc_addr); VMAddress current_address_; VMAddress segment_end_address_; @@ -97,7 +105,7 @@ class ElfImageReader { const ProgramHeaderTable* phdr_table_; // weak std::unique_ptr segment_range_; size_t phdr_index_; - ssize_t max_note_size_; + size_t max_note_size_; std::string name_filter_; NoteType type_filter_; bool use_filter_; @@ -149,6 +157,13 @@ class ElfImageReader { //! The load bias is the actual load address minus the preferred load address. VMOffset GetLoadBias() const { return load_bias_; } + //! \brief Determines the name of this object using `DT_SONAME`, if present. + //! + //! \param[out] name The name of this object, only valid if this method + //! returns `true`. + //! \return `true` if a name was found for this object. + bool SoName(std::string* name); + //! \brief Reads information from the dynamic symbol table about the symbol //! identified by \a name. //! @@ -196,10 +211,9 @@ class ElfImageReader { //! //! \param[in] max_note_size The maximum note size to read. Notes whose //! combined name, descriptor, and padding size are greater than - //! \a max_note_size will be silently skipped. A \a max_note_size of -1 - //! indicates infinite maximum note size. + //! \a max_note_size will be silently skipped. //! \return A NoteReader object capable of reading notes in this image. - std::unique_ptr Notes(ssize_t max_note_size); + std::unique_ptr Notes(size_t max_note_size); //! \brief Return a NoteReader for this image, which scans all PT_NOTE //! segments in the image, filtering by name and type. @@ -211,12 +225,11 @@ class ElfImageReader { //! \param[in] type The note type to match. //! \param[in] max_note_size The maximum note size to read. Notes whose //! combined name, descriptor, and padding size are greater than - //! \a max_note_size will be silently skipped. A \a max_note_size of -1 - //! indicates infinite maximum note size. + //! \a max_note_size will be silently skipped. //! \return A NoteReader object capable of reading notes in this image. std::unique_ptr NotesWithNameAndType(const std::string& name, NoteReader::NoteType type, - ssize_t max_note_size); + size_t max_note_size); //! \brief Return a ProcessMemoryRange restricted to the range of this image. //! diff --git a/snapshot/elf/elf_image_reader_fuzzer.cc b/snapshot/elf/elf_image_reader_fuzzer.cc index 1686650b..b780af4e 100644 --- a/snapshot/elf/elf_image_reader_fuzzer.cc +++ b/snapshot/elf/elf_image_reader_fuzzer.cc @@ -31,7 +31,8 @@ class FakeProcessMemory : public ProcessMemory { VMAddress offset_in_data = address - fake_base_; if (offset_in_data > size_) return -1; - ssize_t read_size = std::min(size_ - offset_in_data, size); + size_t read_size = + std::min(static_cast(size_ - offset_in_data), size); memcpy(buffer, &data_[offset_in_data], read_size); return read_size; } @@ -65,8 +66,10 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { 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)) == + VMAddress desc_addr; + auto notes = reader.Notes(9999); + while ((result = notes->NextNote( + ¬e_name, ¬e_type, ¬e_desc, &desc_addr)) == ElfImageReader::NoteReader::Result::kSuccess) { LOG(ERROR) << note_name << note_type << note_desc; } diff --git a/snapshot/elf/elf_image_reader_fuzzer_corpus/.gitattributes b/snapshot/elf/elf_image_reader_fuzzer_corpus/.gitattributes new file mode 100644 index 00000000..69463d13 --- /dev/null +++ b/snapshot/elf/elf_image_reader_fuzzer_corpus/.gitattributes @@ -0,0 +1,17 @@ +# Copyright 2019 The Crashpad Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ELF executables normally don’t have any extension, so there’s no pattern to +# match in the root .gitattributes file. +/ret42 binary diff --git a/snapshot/elf/elf_image_reader_test.cc b/snapshot/elf/elf_image_reader_test.cc index 11fb1feb..cf8f33b6 100644 --- a/snapshot/elf/elf_image_reader_test.cc +++ b/snapshot/elf/elf_image_reader_test.cc @@ -32,8 +32,7 @@ #include "util/process/process_memory_native.h" #if defined(OS_FUCHSIA) - -#include +#include #include "base/fuchsia/fuchsia_logging.h" @@ -50,8 +49,8 @@ #endif // OS_FUCHSIA extern "C" { -__attribute__((visibility("default"))) void -ElfImageReaderTestExportedSymbol(){}; +__attribute__((visibility("default"))) void ElfImageReaderTestExportedSymbol() { +} } // extern "C" namespace crashpad { @@ -61,14 +60,12 @@ namespace { #if defined(OS_FUCHSIA) -void LocateExecutable(ProcessType process, +void LocateExecutable(const ProcessType& process, ProcessMemory* memory, VMAddress* elf_address) { uintptr_t debug_address; - zx_status_t status = zx_object_get_property(process, - ZX_PROP_PROCESS_DEBUG_ADDR, - &debug_address, - sizeof(debug_address)); + zx_status_t status = process->get_property( + ZX_PROP_PROCESS_DEBUG_ADDR, &debug_address, sizeof(debug_address)); ASSERT_EQ(status, ZX_OK) << "zx_object_get_property: ZX_PROP_PROCESS_DEBUG_ADDR"; // Can be 0 if requested before the loader has loaded anything. @@ -103,10 +100,9 @@ void LocateExecutable(PtraceConnection* connection, ASSERT_TRUE(memory_map.Initialize(connection)); const MemoryMap::Mapping* phdr_mapping = memory_map.FindMapping(phdrs); ASSERT_TRUE(phdr_mapping); - std::vector possible_mappings = - memory_map.FindFilePossibleMmapStarts(*phdr_mapping); - ASSERT_EQ(possible_mappings.size(), 1u); - *elf_address = possible_mappings[0]->range.Base(); + auto possible_mappings = memory_map.FindFilePossibleMmapStarts(*phdr_mapping); + ASSERT_EQ(possible_mappings->Count(), 1u); + *elf_address = possible_mappings->Next()->range.Base(); } #endif // OS_FUCHSIA @@ -157,22 +153,24 @@ void ReadThisExecutableInTarget(ProcessType process, std::string note_name; std::string note_desc; ElfImageReader::NoteReader::NoteType note_type; + VMAddress desc_addr; - std::unique_ptr notes = reader.Notes(-1); - while ((result = notes->NextNote(¬e_name, ¬e_type, ¬e_desc)) == + std::unique_ptr notes = reader.Notes(10000); + while ((result = notes->NextNote( + ¬e_name, ¬e_type, ¬e_desc, &desc_addr)) == 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), + EXPECT_EQ(notes->NextNote(¬e_name, ¬e_type, ¬e_desc, &desc_addr), ElfImageReader::NoteReader::Result::kNoMoreNotes); // Find the note defined in elf_image_reader_test_note.S. constexpr uint32_t kCrashpadNoteDesc = 42; notes = reader.NotesWithNameAndType( - CRASHPAD_ELF_NOTE_NAME, CRASHPAD_ELF_NOTE_TYPE_SNAPSHOT_TEST, -1); - ASSERT_EQ(notes->NextNote(¬e_name, ¬e_type, ¬e_desc), + CRASHPAD_ELF_NOTE_NAME, CRASHPAD_ELF_NOTE_TYPE_SNAPSHOT_TEST, 10000); + ASSERT_EQ(notes->NextNote(¬e_name, ¬e_type, ¬e_desc, &desc_addr), ElfImageReader::NoteReader::Result::kSuccess); EXPECT_EQ(note_name, CRASHPAD_ELF_NOTE_NAME); EXPECT_EQ(note_type, @@ -181,7 +179,7 @@ void ReadThisExecutableInTarget(ProcessType process, EXPECT_EQ(*reinterpret_cast(¬e_desc[0]), kCrashpadNoteDesc); - EXPECT_EQ(notes->NextNote(¬e_name, ¬e_type, ¬e_desc), + EXPECT_EQ(notes->NextNote(¬e_name, ¬e_type, ¬e_desc, &desc_addr), ElfImageReader::NoteReader::Result::kNoMoreNotes); } diff --git a/snapshot/elf/module_snapshot_elf.cc b/snapshot/elf/module_snapshot_elf.cc index 62a961d3..037e7cdb 100644 --- a/snapshot/elf/module_snapshot_elf.cc +++ b/snapshot/elf/module_snapshot_elf.cc @@ -20,6 +20,7 @@ #include "base/files/file_path.h" #include "snapshot/crashpad_types/image_annotation_reader.h" +#include "snapshot/memory_snapshot_generic.h" #include "util/misc/elf_note_types.h" namespace crashpad { @@ -28,14 +29,17 @@ namespace internal { ModuleSnapshotElf::ModuleSnapshotElf(const std::string& name, ElfImageReader* elf_reader, ModuleSnapshot::ModuleType type, - ProcessMemoryRange* process_memory_range) + ProcessMemoryRange* process_memory_range, + const ProcessMemory* process_memory) : ModuleSnapshot(), name_(name), elf_reader_(elf_reader), process_memory_range_(process_memory_range), + process_memory_(process_memory), crashpad_info_(), type_(type), - initialized_() {} + initialized_(), + streams_() {} ModuleSnapshotElf::~ModuleSnapshotElf() = default; @@ -56,9 +60,17 @@ bool ModuleSnapshotElf::Initialize() { kMaxNoteSize); std::string desc; VMAddress info_address; - if (notes->NextNote(nullptr, nullptr, &desc) == + VMAddress desc_address; + if (notes->NextNote(nullptr, nullptr, &desc, &desc_address) == ElfImageReader::NoteReader::Result::kSuccess) { - info_address = *reinterpret_cast(&desc[0]); + VMOffset offset; + if (elf_reader_->Memory()->Is64Bit()) { + offset = *reinterpret_cast(&desc[0]); + } else { + int32_t offset32 = *reinterpret_cast(&desc[0]); + offset = offset32; + } + info_address = desc_address + offset; ProcessMemoryRange range; if (range.Initialize(*elf_reader_->Memory())) { @@ -142,12 +154,10 @@ 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])); + auto build_id = BuildID(); + build_id.insert( + build_id.end(), 16 - std::min(build_id.size(), size_t{16}), '\0'); + uuid->InitializeFromBytes(build_id.data()); // TODO(scottmg): https://crashpad.chromium.org/bug/229. These are // endian-swapped to match FileID::ConvertIdentifierToUUIDString() in @@ -162,6 +172,21 @@ std::string ModuleSnapshotElf::DebugFileName() const { return base::FilePath(Name()).BaseName().value(); } +std::vector ModuleSnapshotElf::BuildID() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + std::unique_ptr notes = + elf_reader_->NotesWithNameAndType(ELF_NOTE_GNU, NT_GNU_BUILD_ID, 64); + std::string desc; + VMAddress desc_addr; + notes->NextNote(nullptr, nullptr, &desc, &desc_addr); + + std::vector build_id; + build_id.reserve(desc.size()); + std::copy(desc.begin(), desc.end(), std::back_inserter(build_id)); + return build_id; +} + std::vector ModuleSnapshotElf::AnnotationsVector() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return std::vector(); @@ -195,7 +220,33 @@ std::set> ModuleSnapshotElf::ExtraMemoryRanges() const { std::vector ModuleSnapshotElf::CustomMinidumpStreams() const { - return std::vector(); + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + streams_.clear(); + + std::vector result; + if (!crashpad_info_) + return result; + + for (uint64_t cur = crashpad_info_->UserDataMinidumpStreamHead(); cur;) { + internal::UserDataMinidumpStreamListEntry list_entry; + if (!process_memory_->Read(cur, sizeof(list_entry), &list_entry)) { + LOG(WARNING) << "could not read user data stream entry from " << name_; + return result; + } + + if (list_entry.size != 0) { + auto memory = std::make_unique(); + memory->Initialize( + process_memory_, list_entry.base_address, list_entry.size); + streams_.push_back(std::make_unique( + list_entry.stream_type, memory.release())); + result.push_back(streams_.back().get()); + } + + cur = list_entry.next; + } + + return result; } } // namespace internal diff --git a/snapshot/elf/module_snapshot_elf.h b/snapshot/elf/module_snapshot_elf.h index 15c3f3c2..67ecdf7e 100644 --- a/snapshot/elf/module_snapshot_elf.h +++ b/snapshot/elf/module_snapshot_elf.h @@ -41,11 +41,15 @@ class ModuleSnapshotElf final : public ModuleSnapshot { //! \param[in] name The pathname used to load the module from disk. //! \param[in] elf_reader An image reader for the module. //! \param[in] type The module's type. - //! \param[in] process_memory_range A memory reader for the target process. + //! \param[in] process_memory_range A memory reader giving protected access + //! to the target process. + //! \param[in] process_memory A memory reader for the target process which can + //! be used to initialize a MemorySnapshot. ModuleSnapshotElf(const std::string& name, ElfImageReader* elf_reader, ModuleSnapshot::ModuleType type, - ProcessMemoryRange* process_memory_range); + ProcessMemoryRange* process_memory_range, + const ProcessMemory* process_memory); ~ModuleSnapshotElf() override; //! \brief Initializes the object. @@ -77,6 +81,7 @@ class ModuleSnapshotElf final : public ModuleSnapshot { ModuleType GetModuleType() const override; void UUIDAndAge(crashpad::UUID* uuid, uint32_t* age) const override; std::string DebugFileName() const override; + std::vector BuildID() const override; std::vector AnnotationsVector() const override; std::map AnnotationsSimpleMap() const override; std::vector AnnotationObjects() const override; @@ -87,9 +92,12 @@ class ModuleSnapshotElf final : public ModuleSnapshot { std::string name_; ElfImageReader* elf_reader_; ProcessMemoryRange* process_memory_range_; + const ProcessMemory* process_memory_; std::unique_ptr crashpad_info_; ModuleType type_; InitializationStateDcheck initialized_; + // Too const-y: https://crashpad.chromium.org/bug/9. + mutable std::vector> streams_; DISALLOW_COPY_AND_ASSIGN(ModuleSnapshotElf); }; diff --git a/snapshot/fuchsia/cpu_context_fuchsia.cc b/snapshot/fuchsia/cpu_context_fuchsia.cc index 5a02f62f..226bfc67 100644 --- a/snapshot/fuchsia/cpu_context_fuchsia.cc +++ b/snapshot/fuchsia/cpu_context_fuchsia.cc @@ -21,7 +21,7 @@ namespace internal { #if defined(ARCH_CPU_X86_64) -void InitializeCPUContextX86_64( +void InitializeCPUContextX86_64_NoFloatingPoint( const zx_thread_state_general_regs_t& thread_context, CPUContextX86_64* context) { memset(context, 0, sizeof(*context)); @@ -45,6 +45,50 @@ void InitializeCPUContextX86_64( context->rflags = thread_context.rflags; } +#elif defined(ARCH_CPU_ARM64) + +void InitializeCPUContextARM64( + const zx_thread_state_general_regs_t& thread_context, + const zx_thread_state_vector_regs_t& vector_context, + CPUContextARM64* context) { + memset(context, 0, sizeof(*context)); + + // Fuchsia stores the link register (x30) on its own while Crashpad stores it + // with the other general purpose x0-x28 and x29 frame pointer registers. So + // we expect the size and number of elements to be off by one unit. + static_assert(sizeof(context->regs) - sizeof(context->regs[30]) == + sizeof(thread_context.r), + "registers size mismatch"); + static_assert((sizeof(context->regs) - sizeof(context->regs[30])) / + sizeof(context->regs[0]) == + sizeof(thread_context.r) / sizeof(thread_context.r[0]), + "registers number of elements mismatch"); + memcpy(&context->regs, &thread_context.r, sizeof(thread_context.r)); + context->regs[30] = thread_context.lr; + context->sp = thread_context.sp; + context->pc = thread_context.pc; + + // Only the NZCV flags (bits 31 to 28 respectively) of the cpsr register are + // readable and writable by userland on ARM64. + constexpr uint32_t kNZCV = 0xf0000000; + // Fuchsia uses the old "cspr" terminology from armv7 while Crashpad uses the + // new "spsr" terminology for armv8. + context->spsr = thread_context.cpsr & kNZCV; + if (thread_context.cpsr > + std::numeric_limitsspsr)>::max()) { + LOG(WARNING) << "cpsr truncation: we only expect the first 32 bits to be " + "set in the cpsr"; + } + context->spsr = + static_castspsr)>(thread_context.cpsr) & kNZCV; + + context->fpcr = vector_context.fpcr; + context->fpsr = vector_context.fpsr; + static_assert(sizeof(context->fpsimd) == sizeof(vector_context.v), + "registers size mismatch"); + memcpy(&context->fpsimd, &vector_context.v, sizeof(vector_context.v)); +} + #endif // ARCH_CPU_X86_64 } // namespace internal diff --git a/snapshot/fuchsia/cpu_context_fuchsia.h b/snapshot/fuchsia/cpu_context_fuchsia.h index f5336fdd..9227bfc8 100644 --- a/snapshot/fuchsia/cpu_context_fuchsia.h +++ b/snapshot/fuchsia/cpu_context_fuchsia.h @@ -34,12 +34,28 @@ namespace internal { //! //! \param[in] thread_context The native thread context. //! \param[out] context The CPUContextX86_64 structure to initialize. -void InitializeCPUContextX86_64( +void InitializeCPUContextX86_64_NoFloatingPoint( const zx_thread_state_general_regs_t& thread_context, CPUContextX86_64* context); #endif // ARCH_CPU_X86_64 || DOXYGEN +#if defined(ARCH_CPU_ARM64) || DOXYGEN + +//! \brief Initializes a CPUContextARM64 structure from native context +//! structures on Fuchsia. +//! +//! \param[in] thread_context The native thread context. +//! \param[in] vector_context The native vector context that also contains the +//! floating point registers. +//! \param[out] context The CPUContextARM64 structure to initialize. +void InitializeCPUContextARM64( + const zx_thread_state_general_regs_t& thread_context, + const zx_thread_state_vector_regs_t& vector_context, + CPUContextARM64* context); + +#endif // ARCH_CPU_ARM64 || DOXYGEN + } // namespace internal } // namespace crashpad diff --git a/snapshot/fuchsia/exception_snapshot_fuchsia.cc b/snapshot/fuchsia/exception_snapshot_fuchsia.cc index 44b4e5cd..f70b9660 100644 --- a/snapshot/fuchsia/exception_snapshot_fuchsia.cc +++ b/snapshot/fuchsia/exception_snapshot_fuchsia.cc @@ -59,13 +59,14 @@ void ExceptionSnapshotFuchsia::Initialize( #if defined(ARCH_CPU_X86_64) context_.architecture = kCPUArchitectureX86_64; context_.x86_64 = &context_arch_; - // TODO(scottmg): Float context, once Fuchsia has a debug API to capture - // floating point registers. ZX-1750 upstream. - InitializeCPUContextX86_64(t.general_registers, context_.x86_64); + // TODO(fuchsia/DX-642): Add float context once saved in |t|. + InitializeCPUContextX86_64_NoFloatingPoint(t.general_registers, + context_.x86_64); #elif defined(ARCH_CPU_ARM64) context_.architecture = kCPUArchitectureARM64; context_.arm64 = &context_arch_; - // TODO(scottmg): Implement context capture for arm64. + InitializeCPUContextARM64( + t.general_registers, t.vector_registers, context_.arm64); #else #error Port. #endif @@ -85,7 +86,6 @@ void ExceptionSnapshotFuchsia::Initialize( #endif } - INITIALIZATION_STATE_SET_VALID(initialized_); } diff --git a/snapshot/fuchsia/memory_map_fuchsia.cc b/snapshot/fuchsia/memory_map_fuchsia.cc index b60531d0..15867929 100644 --- a/snapshot/fuchsia/memory_map_fuchsia.cc +++ b/snapshot/fuchsia/memory_map_fuchsia.cc @@ -14,8 +14,6 @@ #include "snapshot/fuchsia/memory_map_fuchsia.h" -#include - #include "base/fuchsia/fuchsia_logging.h" #include "util/numeric/checked_range.h" @@ -25,7 +23,7 @@ MemoryMapFuchsia::MemoryMapFuchsia() = default; MemoryMapFuchsia::~MemoryMapFuchsia() = default; -bool MemoryMapFuchsia::Initialize(zx_handle_t process) { +bool MemoryMapFuchsia::Initialize(const zx::process& process) { INITIALIZATION_STATE_SET_INITIALIZING(initialized_); // There's no way to know what an appropriate buffer size is before starting. @@ -40,12 +38,11 @@ bool MemoryMapFuchsia::Initialize(zx_handle_t process) { size_t actual; size_t available; zx_status_t status = - zx_object_get_info(process, - ZX_INFO_PROCESS_MAPS, - &map_entries_[0], - map_entries_.size() * sizeof(map_entries_[0]), - &actual, - &available); + process.get_info(ZX_INFO_PROCESS_MAPS, + &map_entries_[0], + map_entries_.size() * sizeof(map_entries_[0]), + &actual, + &available); if (status != ZX_OK) { ZX_LOG(ERROR, status) << "zx_object_get_info ZX_INFO_PROCESS_MAPS"; map_entries_.clear(); diff --git a/snapshot/fuchsia/memory_map_fuchsia.h b/snapshot/fuchsia/memory_map_fuchsia.h index 88df5695..cdbdb179 100644 --- a/snapshot/fuchsia/memory_map_fuchsia.h +++ b/snapshot/fuchsia/memory_map_fuchsia.h @@ -15,6 +15,7 @@ #ifndef CRASHPAD_SNAPSHOT_FUCHSIA_MEMORY_MAP_FUCHSIA_H_ #define CRASHPAD_SNAPSHOT_FUCHSIA_MEMORY_MAP_FUCHSIA_H_ +#include #include #include @@ -33,7 +34,7 @@ class MemoryMapFuchsia { //! regions in the given process. //! //! \return `true` on success, or `false`, with an error logged. - bool Initialize(zx_handle_t process); + bool Initialize(const zx::process& process); //! \brief Searches through the previously retrieved memory map for the given //! address. If found, returns the deepest `zx_info_maps_t` mapping that @@ -45,6 +46,10 @@ class MemoryMapFuchsia { //! will be filled out, otherwise `false` and \a map will be unchanged. bool FindMappingForAddress(zx_vaddr_t address, zx_info_maps_t* map) const; + //! \brief Get a vector of `zx_info_maps_t` representing the memory map for + //! this process. + const std::vector& Entries() const { return map_entries_; } + private: std::vector map_entries_; InitializationStateDcheck initialized_; diff --git a/snapshot/fuchsia/memory_map_region_snapshot_fuchsia.cc b/snapshot/fuchsia/memory_map_region_snapshot_fuchsia.cc new file mode 100644 index 00000000..1133c4d4 --- /dev/null +++ b/snapshot/fuchsia/memory_map_region_snapshot_fuchsia.cc @@ -0,0 +1,84 @@ +// Copyright 2018 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "snapshot/fuchsia/memory_map_region_snapshot_fuchsia.h" + +#include "base/logging.h" +#include "base/stl_util.h" + +namespace crashpad { +namespace internal { + +namespace { + +// Maps from bitwise OR of Zircon's flags to enumerated Windows version. +uint32_t MmuFlagsToProtectFlags(zx_vm_option_t flags) { + // These bits are currently the lowest 3 of zx_vm_option_t. They're used to + // index into a mapping array, so make sure that we notice if they change + // values. + static_assert( + ZX_VM_PERM_READ == 1 && ZX_VM_PERM_WRITE == 2 && ZX_VM_PERM_EXECUTE == 4, + "table below will need fixing"); + + // The entries set to zero don't have good corresponding Windows minidump + // names. They also aren't currently supported by the mapping syscall on + // Zircon, so DCHECK that they don't happen in practice. EXECUTE-only also + // cannot currently happen, but as that has a good mapping to the Windows + // value, leave it in place in case it's supported by the syscall in the + // future. + static constexpr uint32_t mapping[] = { + /* --- */ PAGE_NOACCESS, + /* --r */ PAGE_READONLY, + /* -w- */ 0, + /* -wr */ PAGE_READWRITE, + /* x-- */ PAGE_EXECUTE, + /* x-r */ PAGE_EXECUTE_READ, + /* xw- */ 0, + /* xwr */ PAGE_EXECUTE_READWRITE, + }; + + const uint32_t index = + flags & (ZX_VM_PERM_READ | ZX_VM_PERM_WRITE | ZX_VM_PERM_EXECUTE); + DCHECK_LT(index, base::size(mapping)); + + const uint32_t protect_flags = mapping[index]; + DCHECK_NE(protect_flags, 0u); + return protect_flags; +} + +} // namespace + +MemoryMapRegionSnapshotFuchsia::MemoryMapRegionSnapshotFuchsia( + const zx_info_maps_t& info_map) + : memory_info_() { + DCHECK_EQ(info_map.type, ZX_INFO_MAPS_TYPE_MAPPING); + + memory_info_.BaseAddress = info_map.base; + memory_info_.AllocationBase = info_map.base; + memory_info_.RegionSize = info_map.size; + memory_info_.State = MEM_COMMIT; + memory_info_.Protect = memory_info_.AllocationProtect = + MmuFlagsToProtectFlags(info_map.u.mapping.mmu_flags); + memory_info_.Type = MEM_MAPPED; +} + +MemoryMapRegionSnapshotFuchsia::~MemoryMapRegionSnapshotFuchsia() {} + +const MINIDUMP_MEMORY_INFO& +MemoryMapRegionSnapshotFuchsia::AsMinidumpMemoryInfo() const { + return memory_info_; +} + +} // namespace internal +} // namespace crashpad diff --git a/snapshot/fuchsia/memory_map_region_snapshot_fuchsia.h b/snapshot/fuchsia/memory_map_region_snapshot_fuchsia.h new file mode 100644 index 00000000..37f7a1a7 --- /dev/null +++ b/snapshot/fuchsia/memory_map_region_snapshot_fuchsia.h @@ -0,0 +1,39 @@ +// Copyright 2018 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_SNAPSHOT_FUCHSIA_MEMORY_MAP_REGION_SNAPSHOT_FUCHSIA_H_ +#define CRASHPAD_SNAPSHOT_FUCHSIA_MEMORY_MAP_REGION_SNAPSHOT_FUCHSIA_H_ + +#include "snapshot/memory_map_region_snapshot.h" + +#include + +namespace crashpad { +namespace internal { + +class MemoryMapRegionSnapshotFuchsia : public MemoryMapRegionSnapshot { + public: + explicit MemoryMapRegionSnapshotFuchsia(const zx_info_maps_t& info_map); + ~MemoryMapRegionSnapshotFuchsia() override; + + virtual const MINIDUMP_MEMORY_INFO& AsMinidumpMemoryInfo() const override; + + private: + MINIDUMP_MEMORY_INFO memory_info_; +}; + +} // namespace internal +} // namespace crashpad + +#endif // CRASHPAD_SNAPSHOT_FUCHSIA_MEMORY_MAP_REGION_SNAPSHOT_FUCHSIA_H_ diff --git a/snapshot/fuchsia/process_reader_fuchsia.cc b/snapshot/fuchsia/process_reader_fuchsia.cc index 0a6a84bb..4522e511 100644 --- a/snapshot/fuchsia/process_reader_fuchsia.cc +++ b/snapshot/fuchsia/process_reader_fuchsia.cc @@ -14,11 +14,11 @@ #include "snapshot/fuchsia/process_reader_fuchsia.h" +#include #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" @@ -53,16 +53,40 @@ void GetStackRegions( } if (range_with_sp.type != ZX_INFO_MAPS_TYPE_MAPPING) { - LOG(ERROR) << "stack range has unexpected type, continuing anyway"; + LOG(ERROR) << "stack range has unexpected type " << range_with_sp.type + << ", aborting"; + return; } - if (range_with_sp.u.mapping.mmu_flags & ZX_VM_FLAG_PERM_EXECUTE) { + if (range_with_sp.u.mapping.mmu_flags & ZX_VM_PERM_EXECUTE) { LOG(ERROR) << "stack range is unexpectedly marked executable, continuing anyway"; } + // The stack covers [range_with_sp.base, range_with_sp.base + + // range_with_sp.size). The stack pointer (sp) can be anywhere in that range. + // It starts at the end of the range (range_with_sp.base + range_with_sp.size) + // and goes downwards until range_with_sp.base. Capture the part of the stack + // that is currently used: [sp, range_with_sp.base + range_with_sp.size). + + // Capture up to kExtraCaptureSize additional bytes of stack, but only if + // present in the region that was already found. + constexpr uint64_t kExtraCaptureSize = 128; + const uint64_t start_address = + std::max(sp >= kExtraCaptureSize ? sp - kExtraCaptureSize : sp, + range_with_sp.base); + const size_t region_size = + range_with_sp.size - (start_address - range_with_sp.base); + + // Because most Fuchsia processes use safestack, it is very unlikely that a + // stack this large would be valid. Even if it were, avoid creating + // unreasonably large dumps by artificially limiting the captured amount. + constexpr uint64_t kMaxStackCapture = 1048576u; + LOG_IF(ERROR, region_size > kMaxStackCapture) + << "clamping unexpectedly large stack capture of " << region_size; + const size_t clamped_region_size = std::min(region_size, kMaxStackCapture); stack_regions->push_back( - CheckedRange(range_with_sp.base, range_with_sp.size)); + CheckedRange(start_address, clamped_region_size)); // TODO(scottmg): https://crashpad.chromium.org/bug/196, once the retrievable // registers include FS and similar for ARM, retrieve the region for the @@ -83,15 +107,13 @@ ProcessReaderFuchsia::ProcessReaderFuchsia() = default; ProcessReaderFuchsia::~ProcessReaderFuchsia() = default; -bool ProcessReaderFuchsia::Initialize(zx_handle_t process) { +bool ProcessReaderFuchsia::Initialize(const zx::process& process) { INITIALIZATION_STATE_SET_INITIALIZING(initialized_); - process_ = process; + process_ = zx::unowned_process(process); process_memory_.reset(new ProcessMemoryFuchsia()); - process_memory_->Initialize(process_); - - memory_map_.Initialize(process_); + process_memory_->Initialize(*process_); INITIALIZATION_STATE_SET_VALID(initialized_); return true; @@ -119,6 +141,16 @@ ProcessReaderFuchsia::Threads() { return threads_; } +const MemoryMapFuchsia* ProcessReaderFuchsia::MemoryMap() { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + if (!initialized_memory_map_) { + InitializeMemoryMap(); + } + + return memory_map_.get(); +} + void ProcessReaderFuchsia::InitializeModules() { DCHECK(!initialized_modules_); DCHECK(modules_.empty()); @@ -130,27 +162,12 @@ void ProcessReaderFuchsia::InitializeModules() { // retrieves (some of) the data into internal structures. It may be worth // trying to refactor/upstream some of this into Fuchsia. - std::string app_name; - { - char name[ZX_MAX_NAME_LEN]; - zx_status_t status = - zx_object_get_property(process_, ZX_PROP_NAME, name, sizeof(name)); - if (status != ZX_OK) { - LOG(ERROR) << "zx_object_get_property ZX_PROP_NAME"; - return; - } - - app_name = name; - } - // Starting from the ld.so's _dl_debug_addr, read the link_map structure and // walk the list to fill out modules_. uintptr_t debug_address; - zx_status_t status = zx_object_get_property(process_, - ZX_PROP_PROCESS_DEBUG_ADDR, - &debug_address, - sizeof(debug_address)); + zx_status_t status = process_->get_property( + ZX_PROP_PROCESS_DEBUG_ADDR, &debug_address, sizeof(debug_address)); if (status != ZX_OK || debug_address == 0) { LOG(ERROR) << "zx_object_get_property ZX_PROP_PROCESS_DEBUG_ADDR"; return; @@ -206,19 +223,44 @@ void ProcessReaderFuchsia::InitializeModules() { 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. + // Debug symbols are indexed by module name x build-id on the crash server. + // The module name in the indexed Breakpad files is set at build time. So + // Crashpad needs to use the same module name at run time for symbol + // resolution to work properly. + // + // TODO(fuchsia/DX-1234): once Crashpad switches to elf-search, the + // following overwrites won't be necessary as only shared libraries will + // have a soname at runtime, just like at build time. + // + // * For shared libraries, the soname is used as module name at build time, + // which is the dsoname here except for libzircon.so (because it is + // injected by the kernel, its load name is "" and Crashpad needs to + // replace it for symbol resolution to work properly). if (dsoname == "") { dsoname = "libzircon.so"; } + // * For executables and loadable modules, the dummy value "<_>" is used as + // module name at build time. This is because executable and loadable + // modules don't have a name on Fuchsia. So we need to use the same dummy + // value at build and run times. + // Most executables have an empty dsoname. Loadable modules (and some rare + // executables) have a non-empty dsoname starting with a specific prefix, + // which Crashpas can use to identify loadable modules and clear the + // dsoname for them. + static constexpr const char kLoadableModuleLoadNamePrefix[] = " process_memory_range( new ProcessMemoryRange()); // TODO(scottmg): Could this be limited range? - process_memory_range->Initialize(process_memory_.get(), true); - process_memory_ranges_.push_back(std::move(process_memory_range)); + if (process_memory_range->Initialize(process_memory_.get(), true)) { + process_memory_ranges_.push_back(std::move(process_memory_range)); - reader->Initialize(*process_memory_ranges_.back(), base); - module.reader = reader.get(); - module_readers_.push_back(std::move(reader)); - modules_.push_back(module); + if (reader->Initialize(*process_memory_ranges_.back(), base)) { + module.reader = reader.get(); + module_readers_.push_back(std::move(reader)); + modules_.push_back(module); + } + } map = next; } @@ -250,9 +294,9 @@ void ProcessReaderFuchsia::InitializeThreads() { initialized_threads_ = true; std::vector thread_koids = - GetChildKoids(process_, ZX_INFO_PROCESS_THREADS); - std::vector thread_handles = - GetHandlesForChildKoids(process_, thread_koids); + GetChildKoids(*process_, ZX_INFO_PROCESS_THREADS); + std::vector thread_handles = + GetHandlesForThreadKoids(*process_, thread_koids); DCHECK_EQ(thread_koids.size(), thread_handles.size()); for (size_t i = 0; i < thread_handles.size(); ++i) { @@ -261,8 +305,8 @@ void ProcessReaderFuchsia::InitializeThreads() { if (thread_handles[i].is_valid()) { char name[ZX_MAX_NAME_LEN] = {0}; - zx_status_t status = zx_object_get_property( - thread_handles[i].get(), ZX_PROP_NAME, &name, sizeof(name)); + zx_status_t status = + thread_handles[i].get_property(ZX_PROP_NAME, &name, sizeof(name)); if (status != ZX_OK) { ZX_LOG(WARNING, status) << "zx_object_get_property ZX_PROP_NAME"; } else { @@ -270,29 +314,40 @@ void ProcessReaderFuchsia::InitializeThreads() { } zx_info_thread_t thread_info; - status = zx_object_get_info(thread_handles[i].get(), - ZX_INFO_THREAD, - &thread_info, - sizeof(thread_info), - nullptr, - nullptr); + status = thread_handles[i].get_info( + ZX_INFO_THREAD, &thread_info, sizeof(thread_info), nullptr, nullptr); if (status != ZX_OK) { ZX_LOG(WARNING, status) << "zx_object_get_info ZX_INFO_THREAD"; } else { thread.state = thread_info.state; } - zx_thread_state_general_regs_t regs; - status = zx_thread_read_state(thread_handles[i].get(), - ZX_THREAD_STATE_GENERAL_REGS, - ®s, - sizeof(regs)); + zx_thread_state_general_regs_t general_regs; + status = thread_handles[i].read_state( + ZX_THREAD_STATE_GENERAL_REGS, &general_regs, sizeof(general_regs)); if (status != ZX_OK) { - ZX_LOG(WARNING, status) << "zx_thread_read_state"; + ZX_LOG(WARNING, status) + << "zx_thread_read_state(ZX_THREAD_STATE_GENERAL_REGS)"; } else { - thread.general_registers = regs; + thread.general_registers = general_regs; - GetStackRegions(regs, memory_map_, &thread.stack_regions); + const MemoryMapFuchsia* memory_map = MemoryMap(); + if (memory_map) { + // Attempt to retrive stack regions if a memory map was retrieved. In + // particular, this may be null when operating on the current process + // where the memory map will not be able to be retrieved. + GetStackRegions(general_regs, *memory_map, &thread.stack_regions); + } + } + + zx_thread_state_vector_regs_t vector_regs; + status = thread_handles[i].read_state( + ZX_THREAD_STATE_VECTOR_REGS, &vector_regs, sizeof(vector_regs)); + if (status != ZX_OK) { + ZX_LOG(WARNING, status) + << "zx_thread_read_state(ZX_THREAD_STATE_VECTOR_REGS)"; + } else { + thread.vector_registers = vector_regs; } } @@ -300,4 +355,15 @@ void ProcessReaderFuchsia::InitializeThreads() { } } +void ProcessReaderFuchsia::InitializeMemoryMap() { + DCHECK(!initialized_memory_map_); + + initialized_memory_map_ = true; + + memory_map_.reset(new MemoryMapFuchsia); + if (!memory_map_->Initialize(*process_)) { + memory_map_.reset(); + } +} + } // namespace crashpad diff --git a/snapshot/fuchsia/process_reader_fuchsia.h b/snapshot/fuchsia/process_reader_fuchsia.h index 2d0878c3..91c6331c 100644 --- a/snapshot/fuchsia/process_reader_fuchsia.h +++ b/snapshot/fuchsia/process_reader_fuchsia.h @@ -15,6 +15,7 @@ #ifndef CRASHPAD_SNAPSHOT_FUCHSIA_PROCESS_READER_H_ #define CRASHPAD_SNAPSHOT_FUCHSIA_PROCESS_READER_H_ +#include #include #include @@ -76,6 +77,10 @@ class ProcessReaderFuchsia { //! returned by `zx_thread_read_state()`. zx_thread_state_general_regs_t general_registers = {}; + //! \brief The raw architecture-specific `zx_thread_state_vector_regs_t` as + //! returned by `zx_thread_read_state()`. + zx_thread_state_vector_regs_t vector_registers = {}; + //! \brief The regions representing the stack. The first entry in the vector //! represents the callstack, and further entries optionally identify //! other stack data when the thread uses a split stack representation. @@ -94,7 +99,7 @@ class ProcessReaderFuchsia { //! \return `true` on success, indicating that this object will respond //! validly to further method calls. `false` on failure. On failure, no //! further method calls should be made. - bool Initialize(zx_handle_t process); + bool Initialize(const zx::process& process); //! \return The modules loaded in the process. The first element (at index //! `0`) corresponds to the main executable. @@ -104,7 +109,10 @@ class ProcessReaderFuchsia { const std::vector& Threads(); //! \brief Return a memory reader for the target process. - ProcessMemory* Memory() { return process_memory_.get(); } + const ProcessMemory* Memory() const { return process_memory_.get(); } + + //! \brief Return a memory map for the target process. + const MemoryMapFuchsia* MemoryMap(); private: //! Performs lazy initialization of the \a modules_ vector on behalf of @@ -115,15 +123,20 @@ class ProcessReaderFuchsia { //! Threads(). void InitializeThreads(); + //! Performs lazy initialization of the \a memory_map_ on behalf of + //! MemoryMap(). + void InitializeMemoryMap(); + 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_; + std::unique_ptr memory_map_; + zx::unowned_process process_; bool initialized_modules_ = false; bool initialized_threads_ = false; + bool initialized_memory_map_ = false; InitializationStateDcheck initialized_; DISALLOW_COPY_AND_ASSIGN(ProcessReaderFuchsia); diff --git a/snapshot/fuchsia/process_reader_fuchsia_test.cc b/snapshot/fuchsia/process_reader_fuchsia_test.cc index 546d8290..581fb4e4 100644 --- a/snapshot/fuchsia/process_reader_fuchsia_test.cc +++ b/snapshot/fuchsia/process_reader_fuchsia_test.cc @@ -20,6 +20,7 @@ #include #include +#include "base/stl_util.h" #include "gtest/gtest.h" #include "test/multiprocess_exec.h" #include "test/test_paths.h" @@ -31,20 +32,34 @@ namespace { TEST(ProcessReaderFuchsia, SelfBasic) { ProcessReaderFuchsia process_reader; - ASSERT_TRUE(process_reader.Initialize(zx_process_self())); + ASSERT_TRUE(process_reader.Initialize(*zx::process::self())); static constexpr char kTestMemory[] = "Some test memory"; - char buffer[arraysize(kTestMemory)]; + char buffer[base::size(kTestMemory)]; ASSERT_TRUE(process_reader.Memory()->Read( reinterpret_cast(kTestMemory), sizeof(kTestMemory), &buffer)); EXPECT_STREQ(kTestMemory, buffer); const auto& modules = process_reader.Modules(); + // The process should have at least one module, the executable, and then some + // shared libraries, no loadable modules. EXPECT_GT(modules.size(), 0u); + size_t num_executables = 0u; + size_t num_shared_libraries = 0u; for (const auto& module : modules) { EXPECT_FALSE(module.name.empty()); EXPECT_NE(module.type, ModuleSnapshot::kModuleTypeUnknown); + + if (module.type == ModuleSnapshot::kModuleTypeExecutable) { + EXPECT_EQ(module.name, "<_>"); + num_executables++; + } else if (module.type == ModuleSnapshot::kModuleTypeSharedLibrary) { + EXPECT_NE(module.name, "<_>"); + num_shared_libraries++; + } } + EXPECT_EQ(num_executables, 1u); + EXPECT_EQ(num_shared_libraries, modules.size() - num_executables); const auto& threads = process_reader.Threads(); EXPECT_GT(threads.size(), 0u); @@ -82,7 +97,7 @@ class BasicChildTest : public MultiprocessExec { private: void MultiprocessParent() override { ProcessReaderFuchsia process_reader; - ASSERT_TRUE(process_reader.Initialize(ChildProcess())); + ASSERT_TRUE(process_reader.Initialize(*ChildProcess())); zx_vaddr_t addr; ASSERT_TRUE(ReadFileExactly(ReadPipeHandle(), &addr, sizeof(addr))); @@ -149,24 +164,28 @@ class ThreadsChildTest : public MultiprocessExec { ASSERT_TRUE(ReadFileExactly(ReadPipeHandle(), &c, 1)); ASSERT_EQ(c, ' '); - ScopedTaskSuspend suspend(ChildProcess()); + ScopedTaskSuspend suspend(*ChildProcess()); ProcessReaderFuchsia process_reader; - ASSERT_TRUE(process_reader.Initialize(ChildProcess())); + ASSERT_TRUE(process_reader.Initialize(*ChildProcess())); const auto& threads = process_reader.Threads(); EXPECT_EQ(threads.size(), 6u); for (size_t i = 1; i < 6; ++i) { ASSERT_GT(threads[i].stack_regions.size(), 0u); - EXPECT_EQ(threads[i].stack_regions[0].size(), i * 4096u); + EXPECT_GT(threads[i].stack_regions[0].size(), 0u); + EXPECT_LE(threads[i].stack_regions[0].size(), i * 4096u); } } DISALLOW_COPY_AND_ASSIGN(ThreadsChildTest); }; -TEST(ProcessReaderFuchsia, ChildThreads) { +// TODO(scottmg): US-553. ScopedTaskSuspend fails sometimes, with a 50ms +// timeout. Currently unclear how to make that more reliable, so disable the +// test for now as otherwise it flakes. +TEST(ProcessReaderFuchsia, DISABLED_ChildThreads) { ThreadsChildTest test; test.Run(); } diff --git a/snapshot/fuchsia/process_snapshot_fuchsia.cc b/snapshot/fuchsia/process_snapshot_fuchsia.cc index cc66db25..294c9f90 100644 --- a/snapshot/fuchsia/process_snapshot_fuchsia.cc +++ b/snapshot/fuchsia/process_snapshot_fuchsia.cc @@ -14,8 +14,6 @@ #include "snapshot/fuchsia/process_snapshot_fuchsia.h" -#include - #include "base/logging.h" #include "util/fuchsia/koid_utilities.h" @@ -25,7 +23,7 @@ ProcessSnapshotFuchsia::ProcessSnapshotFuchsia() = default; ProcessSnapshotFuchsia::~ProcessSnapshotFuchsia() = default; -bool ProcessSnapshotFuchsia::Initialize(zx_handle_t process) { +bool ProcessSnapshotFuchsia::Initialize(const zx::process& process) { INITIALIZATION_STATE_SET_INITIALIZING(initialized_); if (gettimeofday(&snapshot_time_, nullptr) != 0) { @@ -43,6 +41,16 @@ bool ProcessSnapshotFuchsia::Initialize(zx_handle_t process) { InitializeThreads(); InitializeModules(); + const MemoryMapFuchsia* memory_map = process_reader_.MemoryMap(); + if (memory_map) { + for (const auto& entry : memory_map->Entries()) { + if (entry.type == ZX_INFO_MAPS_TYPE_MAPPING) { + memory_map_.push_back( + std::make_unique(entry)); + } + } + } + INITIALIZATION_STATE_SET_VALID(initialized_); return true; } @@ -93,12 +101,12 @@ void ProcessSnapshotFuchsia::GetCrashpadOptions( *options = local_options; } -pid_t ProcessSnapshotFuchsia::ProcessID() const { +crashpad::ProcessID ProcessSnapshotFuchsia::ProcessID() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - return GetKoidForHandle(zx_process_self()); + return GetKoidForHandle(*zx::process::self()); } -pid_t ProcessSnapshotFuchsia::ParentProcessID() const { +crashpad::ProcessID ProcessSnapshotFuchsia::ParentProcessID() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); NOTREACHED(); // TODO(scottmg): https://crashpad.chromium.org/bug/196 return 0; @@ -177,7 +185,11 @@ const ExceptionSnapshot* ProcessSnapshotFuchsia::Exception() const { std::vector ProcessSnapshotFuchsia::MemoryMap() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - return std::vector(); + std::vector memory_map; + for (const auto& item : memory_map_) { + memory_map.push_back(item.get()); + } + return memory_map; } std::vector ProcessSnapshotFuchsia::Handles() const { @@ -190,6 +202,11 @@ std::vector ProcessSnapshotFuchsia::ExtraMemory() const { return std::vector(); } +const ProcessMemory* ProcessSnapshotFuchsia::Memory() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return process_reader_.Memory(); +} + void ProcessSnapshotFuchsia::InitializeThreads() { const std::vector& process_reader_threads = process_reader_.Threads(); @@ -209,7 +226,8 @@ void ProcessSnapshotFuchsia::InitializeModules() { std::make_unique(reader_module.name, reader_module.reader, reader_module.type, - &memory_range_); + &memory_range_, + process_reader_.Memory()); if (module->Initialize()) { modules_.push_back(std::move(module)); } diff --git a/snapshot/fuchsia/process_snapshot_fuchsia.h b/snapshot/fuchsia/process_snapshot_fuchsia.h index 2590ed58..6af2ebeb 100644 --- a/snapshot/fuchsia/process_snapshot_fuchsia.h +++ b/snapshot/fuchsia/process_snapshot_fuchsia.h @@ -15,9 +15,10 @@ #ifndef CRASHPAD_SNAPSHOT_FUCHSIA_PROCESS_SNAPSHOT_FUCHSIA_H_ #define CRASHPAD_SNAPSHOT_FUCHSIA_PROCESS_SNAPSHOT_FUCHSIA_H_ +#include #include -#include #include +#include #include #include @@ -27,12 +28,14 @@ #include "snapshot/elf/elf_image_reader.h" #include "snapshot/elf/module_snapshot_elf.h" #include "snapshot/fuchsia/exception_snapshot_fuchsia.h" +#include "snapshot/fuchsia/memory_map_region_snapshot_fuchsia.h" #include "snapshot/fuchsia/process_reader_fuchsia.h" #include "snapshot/fuchsia/system_snapshot_fuchsia.h" #include "snapshot/fuchsia/thread_snapshot_fuchsia.h" #include "snapshot/process_snapshot.h" #include "snapshot/unloaded_module_snapshot.h" #include "util/misc/initialization_state_dcheck.h" +#include "util/process/process_id.h" #include "util/process/process_memory_range.h" namespace crashpad { @@ -50,7 +53,7 @@ class ProcessSnapshotFuchsia : public ProcessSnapshot { //! //! \return `true` if the snapshot could be created, `false` otherwise with //! an appropriate message logged. - bool Initialize(zx_handle_t process); + bool Initialize(const zx::process& process); //! \brief Initializes the object's exception. //! @@ -103,8 +106,8 @@ class ProcessSnapshotFuchsia : public ProcessSnapshot { } // ProcessSnapshot: - pid_t ProcessID() const override; - pid_t ParentProcessID() const override; + crashpad::ProcessID ProcessID() const override; + crashpad::ProcessID ParentProcessID() const override; void SnapshotTime(timeval* snapshot_time) const override; void ProcessStartTime(timeval* start_time) const override; void ProcessCPUTimes(timeval* user_time, timeval* system_time) const override; @@ -120,6 +123,7 @@ class ProcessSnapshotFuchsia : public ProcessSnapshot { std::vector MemoryMap() const override; std::vector Handles() const override; std::vector ExtraMemory() const override; + const ProcessMemory* Memory() const override; private: // Initializes threads_ on behalf of Initialize(). @@ -135,6 +139,8 @@ class ProcessSnapshotFuchsia : public ProcessSnapshot { ProcessReaderFuchsia process_reader_; ProcessMemoryRange memory_range_; std::map annotations_simple_map_; + std::vector> + memory_map_; UUID report_id_; UUID client_id_; timeval snapshot_time_; diff --git a/snapshot/fuchsia/process_snapshot_fuchsia_test.cc b/snapshot/fuchsia/process_snapshot_fuchsia_test.cc new file mode 100644 index 00000000..e1b83ac6 --- /dev/null +++ b/snapshot/fuchsia/process_snapshot_fuchsia_test.cc @@ -0,0 +1,235 @@ +// Copyright 2018 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "snapshot/fuchsia/memory_map_region_snapshot_fuchsia.h" + +#include +#include + +#include "base/fuchsia/fuchsia_logging.h" +#include "base/logging.h" +#include "base/stl_util.h" +#include "base/strings/stringprintf.h" +#include "gtest/gtest.h" +#include "snapshot/fuchsia/process_snapshot_fuchsia.h" +#include "test/multiprocess_exec.h" +#include "util/fuchsia/koid_utilities.h" +#include "util/fuchsia/scoped_task_suspend.h" + +namespace crashpad { +namespace test { +namespace { + +constexpr struct { + uint32_t zircon_perm; + size_t pages; + uint32_t minidump_perm; +} kTestMappingPermAndSizes[] = { + // Zircon doesn't currently allow write-only, execute-only, or + // write-execute-only, returning ZX_ERR_INVALID_ARGS on map. + {0, 5, PAGE_NOACCESS}, + {ZX_VM_PERM_READ, 6, PAGE_READONLY}, + // {ZX_VM_PERM_WRITE, 7, PAGE_WRITECOPY}, + // {ZX_VM_PERM_EXECUTE, 8, PAGE_EXECUTE}, + {ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, 9, PAGE_READWRITE}, + {ZX_VM_PERM_READ | ZX_VM_PERM_EXECUTE, 10, PAGE_EXECUTE_READ}, + // {ZX_VM_PERM_WRITE | ZX_VM_PERM_EXECUTE, 11, PAGE_EXECUTE_WRITECOPY}, + {ZX_VM_PERM_READ | ZX_VM_PERM_WRITE | ZX_VM_PERM_EXECUTE, + 12, + PAGE_EXECUTE_READWRITE}, +}; + +CRASHPAD_CHILD_TEST_MAIN(AddressSpaceChildTestMain) { + // Create specifically sized mappings/permissions and write the address in + // our address space to the parent so that the reader can check they're read + // correctly. + for (const auto& t : kTestMappingPermAndSizes) { + zx_handle_t vmo = ZX_HANDLE_INVALID; + const size_t size = t.pages * PAGE_SIZE; + zx_status_t status = zx_vmo_create(size, 0, &vmo); + ZX_CHECK(status == ZX_OK, status) << "zx_vmo_create"; + status = zx_vmo_replace_as_executable(vmo, ZX_HANDLE_INVALID, &vmo); + ZX_CHECK(status == ZX_OK, status) << "zx_vmo_replace_as_executable"; + uintptr_t mapping_addr = 0; + status = zx_vmar_map( + zx_vmar_root_self(), t.zircon_perm, 0, vmo, 0, size, &mapping_addr); + ZX_CHECK(status == ZX_OK, status) << "zx_vmar_map"; + CheckedWriteFile(StdioFileHandle(StdioStream::kStandardOutput), + &mapping_addr, + sizeof(mapping_addr)); + } + + CheckedReadFileAtEOF(StdioFileHandle(StdioStream::kStandardInput)); + return 0; +} + +bool HasSingleMatchingMapping( + const std::vector& memory_map, + uintptr_t address, + size_t size, + uint32_t perm) { + const MemoryMapRegionSnapshot* matching = nullptr; + for (const auto* region : memory_map) { + const MINIDUMP_MEMORY_INFO& mmi = region->AsMinidumpMemoryInfo(); + if (mmi.BaseAddress == address) { + if (matching) { + LOG(ERROR) << "multiple mappings matching address"; + return false; + } + matching = region; + } + } + + if (!matching) + return false; + + const MINIDUMP_MEMORY_INFO& matching_mmi = matching->AsMinidumpMemoryInfo(); + return matching_mmi.Protect == perm && matching_mmi.RegionSize == size; +} + +class AddressSpaceTest : public MultiprocessExec { + public: + AddressSpaceTest() : MultiprocessExec() { + SetChildTestMainFunction("AddressSpaceChildTestMain"); + } + ~AddressSpaceTest() {} + + private: + void MultiprocessParent() override { + uintptr_t test_addresses[base::size(kTestMappingPermAndSizes)]; + for (size_t i = 0; i < base::size(test_addresses); ++i) { + ASSERT_TRUE(ReadFileExactly( + ReadPipeHandle(), &test_addresses[i], sizeof(test_addresses[i]))); + } + + ScopedTaskSuspend suspend(*ChildProcess()); + + ProcessSnapshotFuchsia process_snapshot; + ASSERT_TRUE(process_snapshot.Initialize(*ChildProcess())); + + for (size_t i = 0; i < base::size(test_addresses); ++i) { + const auto& t = kTestMappingPermAndSizes[i]; + EXPECT_TRUE(HasSingleMatchingMapping(process_snapshot.MemoryMap(), + test_addresses[i], + t.pages * PAGE_SIZE, + t.minidump_perm)) + << base::StringPrintf( + "index %zu, zircon_perm 0x%x, minidump_perm 0x%x", + i, + t.zircon_perm, + t.minidump_perm); + } + } + + DISALLOW_COPY_AND_ASSIGN(AddressSpaceTest); +}; + +TEST(ProcessSnapshotFuchsiaTest, AddressSpaceMapping) { + AddressSpaceTest test; + test.Run(); +} + +CRASHPAD_CHILD_TEST_MAIN(StackPointerIntoInvalidLocation) { + // Map a large block, output the base address of it, and block. The parent + // will artificially set the SP into this large block to confirm that a huge + // stack is not accidentally captured. + zx_handle_t large_vmo; + constexpr uint64_t kSize = 1 << 30u; + zx_status_t status = zx_vmo_create(kSize, 0, &large_vmo); + ZX_CHECK(status == ZX_OK, status) << "zx_vmo_create"; + zx_vaddr_t mapped_addr; + status = zx_vmar_map(zx_vmar_root_self(), + ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, + 0, + large_vmo, + 0, + kSize, + &mapped_addr); + ZX_CHECK(status == ZX_OK, status) << "zx_vmar_map"; + CheckedWriteFile(StdioFileHandle(StdioStream::kStandardOutput), + &mapped_addr, + sizeof(mapped_addr)); + zx_nanosleep(ZX_TIME_INFINITE); + return 0; +} + +class InvalidStackPointerTest : public MultiprocessExec { + public: + InvalidStackPointerTest() : MultiprocessExec() { + SetChildTestMainFunction("StackPointerIntoInvalidLocation"); + SetExpectedChildTermination(kTerminationNormal, + ZX_TASK_RETCODE_SYSCALL_KILL); + } + ~InvalidStackPointerTest() {} + + private: + void MultiprocessParent() override { + uint64_t address_of_large_mapping; + ASSERT_TRUE(ReadFileExactly(ReadPipeHandle(), + &address_of_large_mapping, + sizeof(address_of_large_mapping))); + + ScopedTaskSuspend suspend(*ChildProcess()); + + std::vector threads = GetThreadHandles(*ChildProcess()); + ASSERT_EQ(threads.size(), 1u); + + zx_thread_state_general_regs_t regs; + ASSERT_EQ(threads[0].read_state( + ZX_THREAD_STATE_GENERAL_REGS, ®s, sizeof(regs)), + ZX_OK); + + constexpr uint64_t kOffsetIntoMapping = 1024; +#if defined(ARCH_CPU_X86_64) + regs.rsp = address_of_large_mapping + kOffsetIntoMapping; +#elif defined(ARCH_CPU_ARM64) + regs.sp = address_of_large_mapping + kOffsetIntoMapping; +#else +#error +#endif + + ASSERT_EQ(threads[0].write_state( + ZX_THREAD_STATE_GENERAL_REGS, ®s, sizeof(regs)), + ZX_OK); + + ProcessSnapshotFuchsia process_snapshot; + ASSERT_TRUE(process_snapshot.Initialize(*ChildProcess())); + + ASSERT_EQ(process_snapshot.Threads().size(), 1u); + const MemorySnapshot* stack = process_snapshot.Threads()[0]->Stack(); + ASSERT_TRUE(stack); + // Ensure the stack capture isn't unreasonably large. + EXPECT_LT(stack->Size(), 10 * 1048576u); + + // As we've corrupted the child, don't let it run again. + ASSERT_EQ(ChildProcess()->kill(), ZX_OK); + } + + DISALLOW_COPY_AND_ASSIGN(InvalidStackPointerTest); +}; + +// This is a test for a specific failure detailed in +// https://bugs.fuchsia.dev/p/fuchsia/issues/detail?id=41212. A test of stack +// behavior that was intentionally overflowing the stack, and so when Crashpad +// received the exception the SP did not point into the actual stack. This +// caused Crashpad to erronously capture the "stack" from the next mapping in +// the address space (which could be very large, cause OOM, etc.). +TEST(ProcessSnapshotFuchsiaTest, InvalidStackPointer) { + InvalidStackPointerTest test; + test.Run(); +} + +} // namespace +} // namespace test +} // namespace crashpad diff --git a/snapshot/fuchsia/system_snapshot_fuchsia.cc b/snapshot/fuchsia/system_snapshot_fuchsia.cc index 889817ad..c108529b 100644 --- a/snapshot/fuchsia/system_snapshot_fuchsia.cc +++ b/snapshot/fuchsia/system_snapshot_fuchsia.cc @@ -38,10 +38,7 @@ void SystemSnapshotFuchsia::Initialize(const timeval* snapshot_time) { // garnet/bin/uname/uname.c, however, this information isn't provided by // uname(). Additionally, uname() seems to hang if the network is in a bad // state when attempting to retrieve the nodename, so avoid it for now. - char kernel_version[256] = {}; - zx_status_t status = - zx_system_get_version(kernel_version, sizeof(kernel_version)); - ZX_LOG_IF(ERROR, status != ZX_OK, status) << "zx_system_get_version"; + std::string kernel_version = zx_system_get_version_string(); #if defined(ARCH_CPU_X86_64) static constexpr const char kArch[] = "x86_64"; @@ -50,8 +47,8 @@ void SystemSnapshotFuchsia::Initialize(const timeval* snapshot_time) { #else static constexpr const char kArch[] = "unknown"; #endif - os_version_full_ = - base::StringPrintf("Zircon prerelease %s %s", kernel_version, kArch); + os_version_full_ = base::StringPrintf( + "Zircon prerelease %s %s", kernel_version.c_str(), kArch); INITIALIZATION_STATE_SET_VALID(initialized_); } @@ -73,7 +70,7 @@ uint32_t SystemSnapshotFuchsia::CPURevision() const { #if defined(ARCH_CPU_X86_64) return cpuid_.Revision(); #else - NOTREACHED(); // TODO(scottmg): https://crashpad.chromium.org/bug/196. + // TODO(fuchsia/DX-712): Read actual revision. return 0; #endif } @@ -88,7 +85,7 @@ std::string SystemSnapshotFuchsia::CPUVendor() const { #if defined(ARCH_CPU_X86_64) return cpuid_.Vendor(); #else - NOTREACHED(); // TODO(scottmg): https://crashpad.chromium.org/bug/196. + // TODO(fuchsia/DX-712): Read actual vendor. return std::string(); #endif } @@ -106,7 +103,7 @@ uint32_t SystemSnapshotFuchsia::CPUX86Signature() const { #if defined(ARCH_CPU_X86_64) return cpuid_.Signature(); #else - NOTREACHED(); // TODO(scottmg): https://crashpad.chromium.org/bug/196. + NOTREACHED(); return 0; #endif } @@ -116,7 +113,7 @@ uint64_t SystemSnapshotFuchsia::CPUX86Features() const { #if defined(ARCH_CPU_X86_64) return cpuid_.Features(); #else - NOTREACHED(); // TODO(scottmg): https://crashpad.chromium.org/bug/196. + NOTREACHED(); return 0; #endif } @@ -126,7 +123,7 @@ uint64_t SystemSnapshotFuchsia::CPUX86ExtendedFeatures() const { #if defined(ARCH_CPU_X86_64) return cpuid_.ExtendedFeatures(); #else - NOTREACHED(); // TODO(scottmg): https://crashpad.chromium.org/bug/196. + NOTREACHED(); return 0; #endif } @@ -135,7 +132,7 @@ uint32_t SystemSnapshotFuchsia::CPUX86Leaf7Features() const { #if defined(ARCH_CPU_X86_64) return cpuid_.Leaf7Features(); #else - NOTREACHED(); // TODO(scottmg): https://crashpad.chromium.org/bug/196. + NOTREACHED(); return 0; #endif } @@ -145,7 +142,7 @@ bool SystemSnapshotFuchsia::CPUX86SupportsDAZ() const { #if defined(ARCH_CPU_X86_64) return cpuid_.SupportsDAZ(); #else - NOTREACHED(); // TODO(scottmg): https://crashpad.chromium.org/bug/196. + NOTREACHED(); return false; #endif } @@ -191,7 +188,7 @@ bool SystemSnapshotFuchsia::NXEnabled() const { #if defined(ARCH_CPU_X86_64) return cpuid_.NXEnabled(); #else - NOTREACHED(); // TODO(scottmg): https://crashpad.chromium.org/bug/196. + // TODO(fuchsia/DX-712): Read actual NX bit value. return false; #endif } diff --git a/snapshot/fuchsia/thread_snapshot_fuchsia.cc b/snapshot/fuchsia/thread_snapshot_fuchsia.cc index 03055d8c..462cdb1f 100644 --- a/snapshot/fuchsia/thread_snapshot_fuchsia.cc +++ b/snapshot/fuchsia/thread_snapshot_fuchsia.cc @@ -39,21 +39,22 @@ bool ThreadSnapshotFuchsia::Initialize( #if defined(ARCH_CPU_X86_64) context_.architecture = kCPUArchitectureX86_64; context_.x86_64 = &context_arch_; - // TODO(scottmg): Float context, once Fuchsia has a debug API to capture - // floating point registers. ZX-1750 upstream. - InitializeCPUContextX86_64(thread.general_registers, context_.x86_64); + // TODO(fuchsia/DX-642): Add float context once saved in |thread|. + InitializeCPUContextX86_64_NoFloatingPoint(thread.general_registers, + context_.x86_64); #elif defined(ARCH_CPU_ARM64) context_.architecture = kCPUArchitectureARM64; context_.arm64 = &context_arch_; - // TODO(scottmg): Implement context capture for arm64. + InitializeCPUContextARM64( + thread.general_registers, thread.vector_registers, context_.arm64); #else #error Port. #endif if (thread.stack_regions.empty()) { - stack_.Initialize(process_reader, 0, 0); + stack_.Initialize(process_reader->Memory(), 0, 0); } else { - stack_.Initialize(process_reader, + stack_.Initialize(process_reader->Memory(), thread.stack_regions[0].base(), thread.stack_regions[0].size()); // TODO(scottmg): Handle split stack by adding other parts to ExtraMemory(). diff --git a/snapshot/fuchsia/thread_snapshot_fuchsia.h b/snapshot/fuchsia/thread_snapshot_fuchsia.h index db975976..45d4f117 100644 --- a/snapshot/fuchsia/thread_snapshot_fuchsia.h +++ b/snapshot/fuchsia/thread_snapshot_fuchsia.h @@ -67,7 +67,7 @@ class ThreadSnapshotFuchsia final : public ThreadSnapshot { #error Port. #endif CPUContext context_; - MemorySnapshotGeneric stack_; + MemorySnapshotGeneric stack_; zx_koid_t thread_id_; zx_vaddr_t thread_specific_data_address_; InitializationStateDcheck initialized_; diff --git a/snapshot/ios/exception_snapshot_ios.cc b/snapshot/ios/exception_snapshot_ios.cc new file mode 100644 index 00000000..db9e4891 --- /dev/null +++ b/snapshot/ios/exception_snapshot_ios.cc @@ -0,0 +1,264 @@ +// Copyright 2020 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "snapshot/ios/exception_snapshot_ios.h" + +#include "base/logging.h" +#include "base/mac/mach_logging.h" +#include "base/strings/stringprintf.h" +#include "snapshot/cpu_context.h" +#include "snapshot/mac/cpu_context_mac.h" +#include "util/misc/from_pointer_cast.h" + +namespace crashpad { + +namespace internal { + +ExceptionSnapshotIOS::ExceptionSnapshotIOS() + : ExceptionSnapshot(), + context_(), + codes_(), + thread_id_(0), + exception_address_(0), + exception_(0), + exception_info_(0), + initialized_() {} + +ExceptionSnapshotIOS::~ExceptionSnapshotIOS() {} + +void ExceptionSnapshotIOS::InitializeFromSignal(const siginfo_t* siginfo, + const ucontext_t* context) { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + + mcontext_t mcontext = context->uc_mcontext; +#if defined(ARCH_CPU_X86_64) + context_.architecture = kCPUArchitectureX86_64; + context_.x86_64 = &context_x86_64_; + x86_debug_state64_t empty_debug_state = {}; + InitializeCPUContextX86_64(&context_x86_64_, + THREAD_STATE_NONE, + nullptr, + 0, + &mcontext->__ss, + &mcontext->__fs, + &empty_debug_state); +#elif defined(ARCH_CPU_ARM64) + context_.architecture = kCPUArchitectureARM64; + context_.arm64 = &context_arm64_; + InitializeCPUContextARM64(&context_arm64_, + THREAD_STATE_NONE, + nullptr, + 0, + &mcontext->__ss, + &mcontext->__ns); +#endif + + // Thread ID. + thread_identifier_info identifier_info; + mach_msg_type_number_t count = THREAD_IDENTIFIER_INFO_COUNT; + kern_return_t kr = + thread_info(mach_thread_self(), + THREAD_IDENTIFIER_INFO, + reinterpret_cast(&identifier_info), + &count); + if (kr != KERN_SUCCESS) { + MACH_LOG(ERROR, kr) << "thread_identifier_info"; + } else { + thread_id_ = identifier_info.thread_id; + } + + exception_ = siginfo->si_signo; + exception_info_ = siginfo->si_code; + + // TODO(justincohen): Investigate recording more codes_. + + exception_address_ = FromPointerCast(siginfo->si_addr); + + // TODO(justincohen): Record the source of the exception (signal, mach, etc). + + INITIALIZATION_STATE_SET_VALID(initialized_); +} + +void ExceptionSnapshotIOS::InitializeFromMachException( + exception_behavior_t behavior, + thread_t exception_thread, + exception_type_t exception, + const mach_exception_data_type_t* code, + mach_msg_type_number_t code_count, + thread_state_flavor_t flavor, + ConstThreadState state, + mach_msg_type_number_t state_count) { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + + codes_.push_back(exception); + // TODO: rationalize with the macOS implementation. + for (mach_msg_type_number_t code_index = 0; code_index < code_count; + ++code_index) { + codes_.push_back(code[code_index]); + } + exception_ = exception; + exception_info_ = code[0]; + + // For serialization, float_state and, on x86, debug_state, will be identical + // between here and the thread_snapshot version for thread_id. That means + // this block getting float_state and debug_state can be skipped when doing + // proper serialization. +#if defined(ARCH_CPU_X86_64) + x86_thread_state64_t thread_state; + x86_float_state64_t float_state; + x86_debug_state64_t debug_state; + mach_msg_type_number_t thread_state_count = x86_THREAD_STATE64_COUNT; + mach_msg_type_number_t float_state_count = x86_FLOAT_STATE64_COUNT; + mach_msg_type_number_t debug_state_count = x86_DEBUG_STATE64_COUNT; + const thread_state_flavor_t kThreadStateFlavor = x86_THREAD_STATE64; + const thread_state_flavor_t kFloatStateFlavor = x86_FLOAT_STATE64; + const thread_state_flavor_t kDebugStateFlavor = x86_DEBUG_STATE64; +#elif defined(ARCH_CPU_ARM64) + arm_thread_state64_t thread_state; + arm_neon_state64_t float_state; + mach_msg_type_number_t float_state_count = ARM_NEON_STATE64_COUNT; + mach_msg_type_number_t thread_state_count = ARM_THREAD_STATE64_COUNT; + const thread_state_flavor_t kThreadStateFlavor = ARM_THREAD_STATE64; + const thread_state_flavor_t kFloatStateFlavor = ARM_NEON_STATE64; +#endif + + kern_return_t kr = + thread_get_state(exception_thread, + kThreadStateFlavor, + reinterpret_cast(&thread_state), + &thread_state_count); + if (kr != KERN_SUCCESS) { + MACH_LOG(ERROR, kr) << "thread_get_state(" << kThreadStateFlavor << ")"; + } + + kr = thread_get_state(exception_thread, + kFloatStateFlavor, + reinterpret_cast(&float_state), + &float_state_count); + if (kr != KERN_SUCCESS) { + MACH_LOG(ERROR, kr) << "thread_get_state(" << kFloatStateFlavor << ")"; + } + +#if defined(ARCH_CPU_X86_64) + kr = thread_get_state(exception_thread, + kDebugStateFlavor, + reinterpret_cast(&debug_state), + &debug_state_count); + if (kr != KERN_SUCCESS) { + MACH_LOG(ERROR, kr) << "thread_get_state(" << kDebugStateFlavor << ")"; + } +#endif + +#if defined(ARCH_CPU_X86_64) + context_.architecture = kCPUArchitectureX86_64; + context_.x86_64 = &context_x86_64_; + InitializeCPUContextX86_64(&context_x86_64_, + flavor, + state, + state_count, + &thread_state, + &float_state, + &debug_state); +#elif defined(ARCH_CPU_ARM64) + context_.architecture = kCPUArchitectureARM64; + context_.arm64 = &context_arm64_; + InitializeCPUContextARM64( + &context_arm64_, flavor, state, state_count, &thread_state, &float_state); +#endif + + // Thread ID. + thread_identifier_info identifier_info; + mach_msg_type_number_t count = THREAD_IDENTIFIER_INFO_COUNT; + kr = thread_info(mach_thread_self(), + THREAD_IDENTIFIER_INFO, + reinterpret_cast(&identifier_info), + &count); + if (kr != KERN_SUCCESS) { + MACH_LOG(ERROR, kr) << "thread_identifier_info"; + } else { + thread_id_ = identifier_info.thread_id; + } + + // Normally, for EXC_BAD_ACCESS exceptions, the exception address is present + // in code[1]. It may or may not be the instruction pointer address (usually + // it’s not). code[1] may carry the exception address for other exception + // types too, but it’s not guaranteed. But for all other exception types, the + // instruction pointer will be the exception address, and in fact will be + // equal to codes[1] when it’s carrying the exception address. In those cases, + // just use the instruction pointer directly. + bool code_1_is_exception_address = exception_ == EXC_BAD_ACCESS; + +#if defined(ARCH_CPU_X86_64) + // For x86 and x86_64 EXC_BAD_ACCESS exceptions, some code[0] values + // indicate that code[1] does not (or may not) carry the exception address: + // EXC_I386_GPFLT (10.9.5 xnu-2422.115.4/osfmk/i386/trap.c user_trap() for + // T_GENERAL_PROTECTION) and the oddball (VM_PROT_READ | VM_PROT_EXECUTE) + // which collides with EXC_I386_BOUNDFLT (10.9.5 + // xnu-2422.115.4/osfmk/i386/fpu.c fpextovrflt()). Other EXC_BAD_ACCESS + // exceptions come through 10.9.5 xnu-2422.115.4/osfmk/i386/trap.c + // user_page_fault_continue() and do contain the exception address in + // code[1]. + if (exception_ == EXC_BAD_ACCESS && + (exception_info_ == EXC_I386_GPFLT || + exception_info_ == (VM_PROT_READ | VM_PROT_EXECUTE))) { + code_1_is_exception_address = false; + } +#endif + + if (code_1_is_exception_address) { + exception_address_ = code[1]; + } else { + exception_address_ = context_.InstructionPointer(); + } + + INITIALIZATION_STATE_SET_VALID(initialized_); +} + +const CPUContext* ExceptionSnapshotIOS::Context() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return &context_; +} + +uint64_t ExceptionSnapshotIOS::ThreadID() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return thread_id_; +} + +uint32_t ExceptionSnapshotIOS::Exception() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return exception_; +} + +uint32_t ExceptionSnapshotIOS::ExceptionInfo() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return exception_info_; +} + +uint64_t ExceptionSnapshotIOS::ExceptionAddress() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return exception_address_; +} + +const std::vector& ExceptionSnapshotIOS::Codes() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return codes_; +} + +std::vector ExceptionSnapshotIOS::ExtraMemory() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return std::vector(); +} + +} // namespace internal +} // namespace crashpad diff --git a/snapshot/ios/exception_snapshot_ios.h b/snapshot/ios/exception_snapshot_ios.h new file mode 100644 index 00000000..5c1337db --- /dev/null +++ b/snapshot/ios/exception_snapshot_ios.h @@ -0,0 +1,92 @@ +// Copyright 2020 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_SNAPSHOT_IOS_EXCEPTION_SNAPSHOT_IOS_H_ +#define CRASHPAD_SNAPSHOT_IOS_EXCEPTION_SNAPSHOT_IOS_H_ + +#include +#include + +#include + +#include "base/macros.h" +#include "build/build_config.h" +#include "snapshot/cpu_context.h" +#include "snapshot/exception_snapshot.h" +#include "util/mach/mach_extensions.h" +#include "util/misc/initialization_state_dcheck.h" + +namespace crashpad { + +namespace internal { + +//! \brief An ExceptionSnapshot of an exception sustained by a running (or +//! crashed) process on an iOS system. +class ExceptionSnapshotIOS final : public ExceptionSnapshot { + public: + ExceptionSnapshotIOS(); + ~ExceptionSnapshotIOS() override; + + //! \brief Initializes the object from a signal. + //! + //! \return `true` if the snapshot could be created, `false` otherwise with + //! an appropriate message logged. + void InitializeFromSignal(const siginfo_t* siginfo, + const ucontext_t* context); + + //! \brief Initialize the object from a Mach exception for the current task. + //! + //! \return `true` if the snapshot could be created, `false` otherwise with + //! an appropriate message logged. + void InitializeFromMachException(exception_behavior_t behavior, + thread_t exception_thread, + exception_type_t exception, + const mach_exception_data_type_t* code, + mach_msg_type_number_t code_count, + thread_state_flavor_t flavor, + ConstThreadState state, + mach_msg_type_number_t state_count); + // ExceptionSnapshot: + + const CPUContext* Context() const override; + uint64_t ThreadID() const override; + uint32_t Exception() const override; + uint32_t ExceptionInfo() const override; + uint64_t ExceptionAddress() const override; + const std::vector& Codes() const override; + virtual std::vector ExtraMemory() const override; + + private: +#if defined(ARCH_CPU_X86_64) + CPUContextX86_64 context_x86_64_; +#elif defined(ARCH_CPU_ARM64) + CPUContextARM64 context_arm64_; +#else +#error Port. +#endif // ARCH_CPU_X86_64 + CPUContext context_; + std::vector codes_; + uint64_t thread_id_; + uintptr_t exception_address_; + uint32_t exception_; + uint32_t exception_info_; + InitializationStateDcheck initialized_; + + DISALLOW_COPY_AND_ASSIGN(ExceptionSnapshotIOS); +}; + +} // namespace internal +} // namespace crashpad + +#endif // CRASHPAD_SNAPSHOT_IOS_EXCEPTION_SNAPSHOT_IOS_H_ diff --git a/snapshot/ios/memory_snapshot_ios.cc b/snapshot/ios/memory_snapshot_ios.cc new file mode 100644 index 00000000..e760465d --- /dev/null +++ b/snapshot/ios/memory_snapshot_ios.cc @@ -0,0 +1,65 @@ +// Copyright 2020 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "snapshot/ios/memory_snapshot_ios.h" + +namespace crashpad { +namespace internal { + +void MemorySnapshotIOS::Initialize(vm_address_t address, vm_size_t size) { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + address_ = address; + size_ = base::checked_cast(size); + + // TODO(justincohen): This is temporary, as MemorySnapshotIOS will likely be + // able to point directly to the deserialized data dump rather than copying + // data around. + buffer_ = std::unique_ptr(new uint8_t[size_]); + memcpy(buffer_.get(), reinterpret_cast(address_), size_); + INITIALIZATION_STATE_SET_VALID(initialized_); +} + +uint64_t MemorySnapshotIOS::Address() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return address_; +} + +size_t MemorySnapshotIOS::Size() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return size_; +} + +bool MemorySnapshotIOS::Read(Delegate* delegate) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + if (size_ == 0) { + return delegate->MemorySnapshotDelegateRead(nullptr, size_); + } + + return delegate->MemorySnapshotDelegateRead(buffer_.get(), size_); +} + +const MemorySnapshot* MemorySnapshotIOS::MergeWithOtherSnapshot( + const MemorySnapshot* other) const { + CheckedRange merged(0, 0); + if (!LoggingDetermineMergedRange(this, other, &merged)) + return nullptr; + + auto result = std::make_unique(); + result->Initialize(merged.base(), merged.size()); + return result.release(); +} + +} // namespace internal +} // namespace crashpad diff --git a/snapshot/win/memory_snapshot_win.h b/snapshot/ios/memory_snapshot_ios.h similarity index 55% rename from snapshot/win/memory_snapshot_win.h rename to snapshot/ios/memory_snapshot_ios.h index ebc878b8..be991056 100644 --- a/snapshot/win/memory_snapshot_win.h +++ b/snapshot/ios/memory_snapshot_ios.h @@ -1,4 +1,4 @@ -// Copyright 2015 The Crashpad Authors. All rights reserved. +// Copyright 2020 The Crashpad Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,43 +12,30 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef CRASHPAD_SNAPSHOT_WIN_MEMORY_SNAPSHOT_WIN_H_ -#define CRASHPAD_SNAPSHOT_WIN_MEMORY_SNAPSHOT_WIN_H_ - -#include -#include +#ifndef CRASHPAD_SNAPSHOT_IOS_MEMORY_SNAPSHOT_IOS_H_ +#define CRASHPAD_SNAPSHOT_IOS_MEMORY_SNAPSHOT_IOS_H_ #include "base/macros.h" #include "snapshot/memory_snapshot.h" -#include "snapshot/win/process_reader_win.h" +#include "util/misc/address_types.h" #include "util/misc/initialization_state_dcheck.h" namespace crashpad { namespace internal { -//! \brief A MemorySnapshot of a memory region in a process on the running -//! system, when the system runs Windows. -class MemorySnapshotWin final : public MemorySnapshot { +//! \brief A MemorySnapshot of a memory region. +class MemorySnapshotIOS final : public MemorySnapshot { public: - MemorySnapshotWin(); - ~MemorySnapshotWin() override; + MemorySnapshotIOS() = default; + ~MemorySnapshotIOS() = 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] address The base address of the memory region to snapshot. //! \param[in] size The size of the memory region to snapshot. - void Initialize(ProcessReaderWin* process_reader, - uint64_t address, - uint64_t size); + void Initialize(vm_address_t address, vm_size_t size); // MemorySnapshot: - uint64_t Address() const override; size_t Size() const override; bool Read(Delegate* delegate) const override; @@ -61,15 +48,16 @@ class MemorySnapshotWin final : public MemorySnapshot { const T* self, const MemorySnapshot* other); - ProcessReaderWin* process_reader_; // weak - uint64_t address_; - size_t size_; + // TODO(justincohen): This is temporary until deserialization is worked out. + std::unique_ptr buffer_; + vm_address_t address_; + vm_size_t size_; InitializationStateDcheck initialized_; - DISALLOW_COPY_AND_ASSIGN(MemorySnapshotWin); + DISALLOW_COPY_AND_ASSIGN(MemorySnapshotIOS); }; } // namespace internal } // namespace crashpad -#endif // CRASHPAD_SNAPSHOT_WIN_MEMORY_SNAPSHOT_WIN_H_ +#endif // CRASHPAD_SNAPSHOT_IOS_MEMORY_SNAPSHOT_IOS_H_ diff --git a/snapshot/ios/module_snapshot_ios.cc b/snapshot/ios/module_snapshot_ios.cc new file mode 100644 index 00000000..8b251eb9 --- /dev/null +++ b/snapshot/ios/module_snapshot_ios.cc @@ -0,0 +1,238 @@ +// Copyright 2020 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "snapshot/ios/module_snapshot_ios.h" + +#include +#include + +#include "base/files/file_path.h" +#include "base/mac/mach_logging.h" +#include "util/misc/from_pointer_cast.h" +#include "util/misc/uuid.h" + +namespace crashpad { +namespace internal { + +ModuleSnapshotIOS::ModuleSnapshotIOS() + : ModuleSnapshot(), + name_(), + address_(0), + size_(0), + timestamp_(0), + dylib_version_(0), + source_version_(0), + filetype_(0), + initialized_() {} + +ModuleSnapshotIOS::~ModuleSnapshotIOS() {} + +// static. +const dyld_all_image_infos* ModuleSnapshotIOS::DyldAllImageInfo() { + task_dyld_info_data_t dyld_info; + mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT; + + kern_return_t kr = task_info(mach_task_self(), + TASK_DYLD_INFO, + reinterpret_cast(&dyld_info), + &count); + if (kr != KERN_SUCCESS) { + MACH_LOG(WARNING, kr) << "task_info"; + return 0; + } + + return reinterpret_cast(dyld_info.all_image_info_addr); +} + +bool ModuleSnapshotIOS::InitializeDyld(const dyld_all_image_infos* images) { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + + name_ = images->dyldPath; + address_ = FromPointerCast(images->dyldImageLoadAddress); + return FinishInitialization(); +} + +bool ModuleSnapshotIOS::Initialize(const dyld_image_info* image) { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + + name_ = image->imageFilePath; + address_ = FromPointerCast(image->imageLoadAddress); + timestamp_ = image->imageFileModDate; + return FinishInitialization(); +} + +bool ModuleSnapshotIOS::FinishInitialization() { +#ifndef ARCH_CPU_64_BITS +#error Only 64-bit Mach-O is supported +#endif + DCHECK(address_); + const mach_header_64* header = + reinterpret_cast(address_); + const load_command* command = + reinterpret_cast(header + 1); + // Make sure that the basic load command structure doesn’t overflow the + // space allotted for load commands, as well as iterating through ncmds. + for (uint32_t cmd_index = 0, cumulative_cmd_size = 0; + cmd_index <= header->ncmds && cumulative_cmd_size < header->sizeofcmds; + ++cmd_index, cumulative_cmd_size += command->cmdsize) { + if (command->cmd == LC_SEGMENT_64) { + const segment_command_64* segment = + reinterpret_cast(command); + if (strcmp(segment->segname, SEG_TEXT) == 0) { + size_ = segment->vmsize; + } + } else if (command->cmd == LC_ID_DYLIB) { + const dylib_command* dylib = + reinterpret_cast(command); + dylib_version_ = dylib->dylib.current_version; + } else if (command->cmd == LC_SOURCE_VERSION) { + const source_version_command* source_version = + reinterpret_cast(command); + source_version_ = source_version->version; + } else if (command->cmd == LC_UUID) { + const uuid_command* uuid = reinterpret_cast(command); + uuid_.InitializeFromBytes(uuid->uuid); + } + + command = reinterpret_cast( + reinterpret_cast(command) + command->cmdsize); + + // TODO(justincohen): Warn-able things: + // - Bad Mach-O magic (and give up trying to process the module) + // - Unrecognized Mach-O type + // - No SEG_TEXT + // - More than one SEG_TEXT + // - More than one LC_ID_DYLIB, LC_SOURCE_VERSION, or LC_UUID + // - No LC_ID_DYLIB in a dylib file + // - LC_ID_DYLIB in a non-dylib file + // And more optional: + // - Missing LC_UUID (although it leaves us with a big "?") + // - Missing LC_SOURCE_VERSION. + } + + filetype_ = header->filetype; + + INITIALIZATION_STATE_SET_VALID(initialized_); + return true; +} + +std::string ModuleSnapshotIOS::Name() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return name_; +} + +uint64_t ModuleSnapshotIOS::Address() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return address_; +} + +uint64_t ModuleSnapshotIOS::Size() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return size_; +} + +time_t ModuleSnapshotIOS::Timestamp() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return timestamp_; +} + +void ModuleSnapshotIOS::FileVersion(uint16_t* version_0, + uint16_t* version_1, + uint16_t* version_2, + uint16_t* version_3) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + if (filetype_ == MH_DYLIB) { + *version_0 = (dylib_version_ & 0xffff0000) >> 16; + *version_1 = (dylib_version_ & 0x0000ff00) >> 8; + *version_2 = (dylib_version_ & 0x000000ff); + *version_3 = 0; + } else { + *version_0 = 0; + *version_1 = 0; + *version_2 = 0; + *version_3 = 0; + } +} + +void ModuleSnapshotIOS::SourceVersion(uint16_t* version_0, + uint16_t* version_1, + uint16_t* version_2, + uint16_t* version_3) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + *version_0 = (source_version_ & 0xffff000000000000u) >> 48; + *version_1 = (source_version_ & 0x0000ffff00000000u) >> 32; + *version_2 = (source_version_ & 0x00000000ffff0000u) >> 16; + *version_3 = source_version_ & 0x000000000000ffffu; +} + +ModuleSnapshot::ModuleType ModuleSnapshotIOS::GetModuleType() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + switch (filetype_) { + case MH_EXECUTE: + return kModuleTypeExecutable; + case MH_DYLIB: + return kModuleTypeSharedLibrary; + case MH_DYLINKER: + return kModuleTypeDynamicLoader; + case MH_BUNDLE: + return kModuleTypeLoadableModule; + default: + return kModuleTypeUnknown; + } +} + +void ModuleSnapshotIOS::UUIDAndAge(crashpad::UUID* uuid, uint32_t* age) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + *uuid = uuid_; + *age = 0; +} + +std::string ModuleSnapshotIOS::DebugFileName() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return base::FilePath(Name()).BaseName().value(); +} + +std::vector ModuleSnapshotIOS::BuildID() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return std::vector(); +} + +std::vector ModuleSnapshotIOS::AnnotationsVector() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return std::vector(); +} + +std::map ModuleSnapshotIOS::AnnotationsSimpleMap() + const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return std::map(); +} + +std::vector ModuleSnapshotIOS::AnnotationObjects() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return std::vector(); +} + +std::set> ModuleSnapshotIOS::ExtraMemoryRanges() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return std::set>(); +} + +std::vector +ModuleSnapshotIOS::CustomMinidumpStreams() const { + return std::vector(); +} + +} // namespace internal +} // namespace crashpad diff --git a/snapshot/ios/module_snapshot_ios.h b/snapshot/ios/module_snapshot_ios.h new file mode 100644 index 00000000..505c08f8 --- /dev/null +++ b/snapshot/ios/module_snapshot_ios.h @@ -0,0 +1,111 @@ +// Copyright 2020 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_SNAPSHOT_IOS_MODULE_SNAPSHOT_IOS_H_ +#define CRASHPAD_SNAPSHOT_IOS_MODULE_SNAPSHOT_IOS_H_ + +#include +#include +#include + +#include +#include +#include + +#include "base/macros.h" +#include "snapshot/crashpad_info_client_options.h" +#include "snapshot/module_snapshot.h" +#include "util/misc/initialization_state_dcheck.h" + +namespace crashpad { +namespace internal { + +//! \brief A ModuleSnapshot of a code module (binary image) loaded into a +//! running (or crashed) process on an iOS system. +class ModuleSnapshotIOS final : public ModuleSnapshot { + public: + ModuleSnapshotIOS(); + ~ModuleSnapshotIOS() override; + + // TODO(justincohen): This function is temporary, and will be broken into two + // parts. One to do an in-process dump of all the relevant information, and + // two to initialize the snapshot after the in-process dump is loaded. + //! \brief Initializes the object. + //! + //! \param[in] image The mach-o image to be loaded. + //! + //! \return `true` if the snapshot could be created. + bool Initialize(const dyld_image_info* image); + + // TODO(justincohen): This function is temporary, and will be broken into two + // parts. One to do an in-process dump of all the relevant information, and + // two to initialize the snapshot after the in-process dump is loaded. + //! \brief Initializes the object specifically for the dyld module. + //! + //! \param[in] images The structure containing the necessary dyld information. + //! + //! \return `true` if the snapshot could be created. + bool InitializeDyld(const dyld_all_image_infos* images); + + //! \brief Returns options from the module’s CrashpadInfo structure. + //! + //! \param[out] options Options set in the module’s CrashpadInfo structure. + void GetCrashpadOptions(CrashpadInfoClientOptions* options); + + static const dyld_all_image_infos* DyldAllImageInfo(); + + // ModuleSnapshot: + std::string Name() const override; + uint64_t Address() const override; + uint64_t Size() const override; + time_t Timestamp() const override; + void FileVersion(uint16_t* version_0, + uint16_t* version_1, + uint16_t* version_2, + uint16_t* version_3) const override; + void SourceVersion(uint16_t* version_0, + uint16_t* version_1, + uint16_t* version_2, + uint16_t* version_3) const override; + ModuleType GetModuleType() const override; + void UUIDAndAge(UUID* uuid, uint32_t* age) const override; + std::string DebugFileName() const override; + std::vector BuildID() 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: + // Gather the the module information based off of a mach_header_64 |address_|. + bool FinishInitialization(); + + std::string name_; + uint64_t address_; + uint64_t size_; + time_t timestamp_; + uint32_t dylib_version_; + uint64_t source_version_; + uint32_t filetype_; + UUID uuid_; + InitializationStateDcheck initialized_; + + DISALLOW_COPY_AND_ASSIGN(ModuleSnapshotIOS); +}; + +} // namespace internal +} // namespace crashpad + +#endif // CRASHPAD_SNAPSHOT_IOS_MODULE_SNAPSHOT_IOS_H_ diff --git a/snapshot/ios/process_snapshot_ios.cc b/snapshot/ios/process_snapshot_ios.cc new file mode 100644 index 00000000..f45c6ee1 --- /dev/null +++ b/snapshot/ios/process_snapshot_ios.cc @@ -0,0 +1,287 @@ +// Copyright 2020 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "snapshot/ios/process_snapshot_ios.h" + +#include +#include + +#include "base/logging.h" +#include "base/mac/mach_logging.h" +#include "base/stl_util.h" + +namespace { + +void MachTimeValueToTimeval(const time_value& mach, timeval* tv) { + tv->tv_sec = mach.seconds; + tv->tv_usec = mach.microseconds; +} + +} // namespace + +namespace crashpad { + +ProcessSnapshotIOS::ProcessSnapshotIOS() + : ProcessSnapshot(), + kern_proc_info_(), + basic_info_user_time_(), + basic_info_system_time_(), + thread_times_user_time_(), + thread_times_system_time_(), + system_(), + threads_(), + modules_(), + exception_(), + report_id_(), + client_id_(), + annotations_simple_map_(), + snapshot_time_(), + initialized_() {} + +ProcessSnapshotIOS::~ProcessSnapshotIOS() {} + +bool ProcessSnapshotIOS::Initialize(const IOSSystemDataCollector& system_data) { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + + // Used by pid, parent pid and snapshot time. + int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()}; + size_t len = sizeof(kern_proc_info_); + if (sysctl(mib, base::size(mib), &kern_proc_info_, &len, nullptr, 0)) { + PLOG(ERROR) << "sysctl"; + return false; + } + + // Used by user time and system time. + task_basic_info_64 task_basic_info; + mach_msg_type_number_t task_basic_info_count = TASK_BASIC_INFO_64_COUNT; + kern_return_t kr = task_info(mach_task_self(), + TASK_BASIC_INFO_64, + reinterpret_cast(&task_basic_info), + &task_basic_info_count); + if (kr != KERN_SUCCESS) { + MACH_LOG(WARNING, kr) << "task_info TASK_BASIC_INFO_64"; + return false; + } + + task_thread_times_info_data_t task_thread_times; + mach_msg_type_number_t task_thread_times_count = TASK_THREAD_TIMES_INFO_COUNT; + kr = task_info(mach_task_self(), + TASK_THREAD_TIMES_INFO, + reinterpret_cast(&task_thread_times), + &task_thread_times_count); + if (kr != KERN_SUCCESS) { + MACH_LOG(WARNING, kr) << "task_info TASK_THREAD_TIMES"; + } + + basic_info_user_time_ = task_basic_info.user_time; + basic_info_system_time_ = task_basic_info.system_time; + thread_times_user_time_ = task_thread_times.user_time; + thread_times_system_time_ = task_thread_times.system_time; + + if (gettimeofday(&snapshot_time_, nullptr) != 0) { + PLOG(ERROR) << "gettimeofday"; + return false; + } + + system_.Initialize(system_data); + InitializeThreads(); + InitializeModules(); + + INITIALIZATION_STATE_SET_VALID(initialized_); + return true; +} + +void ProcessSnapshotIOS::SetExceptionFromSignal(const siginfo_t* siginfo, + const ucontext_t* context) { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + DCHECK(!exception_.get()); + + exception_.reset(new internal::ExceptionSnapshotIOS()); + exception_->InitializeFromSignal(siginfo, context); +} + +void ProcessSnapshotIOS::SetExceptionFromMachException( + exception_behavior_t behavior, + thread_t exception_thread, + exception_type_t exception, + const mach_exception_data_type_t* code, + mach_msg_type_number_t code_count, + thread_state_flavor_t flavor, + ConstThreadState old_state, + mach_msg_type_number_t old_state_count) { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + DCHECK(!exception_.get()); + + exception_.reset(new internal::ExceptionSnapshotIOS()); + exception_->InitializeFromMachException(behavior, + exception_thread, + exception, + code, + code_count, + flavor, + old_state, + old_state_count); +} + +pid_t ProcessSnapshotIOS::ProcessID() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return kern_proc_info_.kp_proc.p_pid; +} + +pid_t ProcessSnapshotIOS::ParentProcessID() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return kern_proc_info_.kp_eproc.e_ppid; +} + +void ProcessSnapshotIOS::SnapshotTime(timeval* snapshot_time) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + *snapshot_time = snapshot_time_; +} + +void ProcessSnapshotIOS::ProcessStartTime(timeval* start_time) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + *start_time = kern_proc_info_.kp_proc.p_starttime; +} + +void ProcessSnapshotIOS::ProcessCPUTimes(timeval* user_time, + timeval* system_time) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + // Calculate user and system time the same way the kernel does for + // getrusage(). See 10.15.0 xnu-6153.11.26/bsd/kern/kern_resource.c calcru(). + timerclear(user_time); + timerclear(system_time); + + MachTimeValueToTimeval(basic_info_user_time_, user_time); + MachTimeValueToTimeval(basic_info_system_time_, system_time); + + timeval thread_user_time; + MachTimeValueToTimeval(thread_times_user_time_, &thread_user_time); + timeval thread_system_time; + MachTimeValueToTimeval(thread_times_system_time_, &thread_system_time); + + timeradd(user_time, &thread_user_time, user_time); + timeradd(system_time, &thread_system_time, system_time); +} + +void ProcessSnapshotIOS::ReportID(UUID* report_id) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + *report_id = report_id_; +} + +void ProcessSnapshotIOS::ClientID(UUID* client_id) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + *client_id = client_id_; +} + +const std::map& +ProcessSnapshotIOS::AnnotationsSimpleMap() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return annotations_simple_map_; +} + +const SystemSnapshot* ProcessSnapshotIOS::System() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return &system_; +} + +std::vector ProcessSnapshotIOS::Threads() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + std::vector threads; + for (const auto& thread : threads_) { + threads.push_back(thread.get()); + } + return threads; +} + +std::vector ProcessSnapshotIOS::Modules() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + std::vector modules; + for (const auto& module : modules_) { + modules.push_back(module.get()); + } + return modules; +} + +std::vector ProcessSnapshotIOS::UnloadedModules() + const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return std::vector(); +} + +const ExceptionSnapshot* ProcessSnapshotIOS::Exception() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return exception_.get(); +} + +std::vector ProcessSnapshotIOS::MemoryMap() + const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return std::vector(); +} + +std::vector ProcessSnapshotIOS::Handles() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return std::vector(); +} + +std::vector ProcessSnapshotIOS::ExtraMemory() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return std::vector(); +} + +const ProcessMemory* ProcessSnapshotIOS::Memory() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return nullptr; +} + +void ProcessSnapshotIOS::InitializeThreads() { + mach_msg_type_number_t thread_count = 0; + const thread_act_array_t threads = + internal::ThreadSnapshotIOS::GetThreads(&thread_count); + for (uint32_t thread_index = 0; thread_index < thread_count; ++thread_index) { + thread_t thread = threads[thread_index]; + auto thread_snapshot = std::make_unique(); + if (thread_snapshot->Initialize(thread)) { + threads_.push_back(std::move(thread_snapshot)); + } + mach_port_deallocate(mach_task_self(), thread); + } + // TODO(justincohen): This dealloc above and below needs to move with the + // call to task_threads inside internal::ThreadSnapshotIOS::GetThreads. + vm_deallocate(mach_task_self(), + reinterpret_cast(threads), + sizeof(thread_t) * thread_count); +} + +void ProcessSnapshotIOS::InitializeModules() { + const dyld_all_image_infos* image_infos = + internal::ModuleSnapshotIOS::DyldAllImageInfo(); + + uint32_t image_count = image_infos->infoArrayCount; + const dyld_image_info* image_array = image_infos->infoArray; + for (uint32_t image_index = 0; image_index < image_count; ++image_index) { + const dyld_image_info* image = &image_array[image_index]; + auto module = std::make_unique(); + if (module->Initialize(image)) { + modules_.push_back(std::move(module)); + } + } + auto module = std::make_unique(); + if (module->InitializeDyld(image_infos)) { + modules_.push_back(std::move(module)); + } +} + +} // namespace crashpad diff --git a/snapshot/ios/process_snapshot_ios.h b/snapshot/ios/process_snapshot_ios.h new file mode 100644 index 00000000..36b7ba13 --- /dev/null +++ b/snapshot/ios/process_snapshot_ios.h @@ -0,0 +1,122 @@ +// Copyright 2020 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_SNAPSHOT_IOS_PROCESS_SNAPSHOT_IOS_H_ +#define CRASHPAD_SNAPSHOT_IOS_PROCESS_SNAPSHOT_IOS_H_ + +#include + +#include + +#include "snapshot/ios/exception_snapshot_ios.h" +#include "snapshot/ios/module_snapshot_ios.h" +#include "snapshot/ios/system_snapshot_ios.h" +#include "snapshot/ios/thread_snapshot_ios.h" +#include "snapshot/process_snapshot.h" +#include "snapshot/thread_snapshot.h" +#include "snapshot/unloaded_module_snapshot.h" + +namespace crashpad { + +//! \brief A ProcessSnapshot of a running (or crashed) process running on a +//! iphoneOS system. +class ProcessSnapshotIOS final : public ProcessSnapshot { + public: + ProcessSnapshotIOS(); + ~ProcessSnapshotIOS() override; + + //! \brief Initializes the object. + //! + //! \param[in] system_data A class containing various system data points. + //! + //! \return `true` if the snapshot could be created, `false` otherwise with + //! an appropriate message logged. + bool Initialize(const IOSSystemDataCollector& system_data); + + //! \brief Initialize exception information from a signal. + void SetExceptionFromSignal(const siginfo_t* siginfo, + const ucontext_t* context); + + //! \brief Initialize exception information from a Mach exception. + void SetExceptionFromMachException(exception_behavior_t behavior, + thread_t exception_thread, + exception_type_t exception, + const mach_exception_data_type_t* code, + mach_msg_type_number_t code_count, + thread_state_flavor_t flavor, + ConstThreadState old_state, + mach_msg_type_number_t old_state_count); + + //! \brief Sets the value to be returned by ClientID(). + //! + //! On iOS, the client ID is under the control of the snapshot producer, + //! which may call this method to set the client ID. If this is not done, + //! ClientID() will return an identifier consisting entirely of zeroes. + void SetClientID(const UUID& client_id) { client_id_ = client_id; } + + //! \brief Sets the value to be returned by ReportID(). + //! + //! On iOS, the crash report ID is under the control of the snapshot + //! producer, which may call this method to set the report ID. If this is not + //! done, ReportID() will return an identifier consisting entirely of zeroes. + void SetReportID(const UUID& report_id) { report_id_ = report_id; } + + // ProcessSnapshot: + pid_t ProcessID() const override; + pid_t ParentProcessID() const override; + void SnapshotTime(timeval* snapshot_time) const override; + void ProcessStartTime(timeval* start_time) const override; + void ProcessCPUTimes(timeval* user_time, timeval* system_time) const override; + void ReportID(UUID* report_id) const override; + void ClientID(UUID* client_id) const override; + const std::map& 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; + const ProcessMemory* Memory() const override; + + private: + // Initializes modules_ on behalf of Initialize(). + void InitializeModules(); + + // Initializes threads_ on behalf of Initialize(). + void InitializeThreads(); + + kinfo_proc kern_proc_info_; + time_value_t basic_info_user_time_; + time_value_t basic_info_system_time_; + time_value_t thread_times_user_time_; + time_value_t thread_times_system_time_; + internal::SystemSnapshotIOS system_; + std::vector> threads_; + std::vector> modules_; + std::unique_ptr exception_; + UUID report_id_; + UUID client_id_; + std::map annotations_simple_map_; + timeval snapshot_time_; + InitializationStateDcheck initialized_; + + DISALLOW_COPY_AND_ASSIGN(ProcessSnapshotIOS); +}; + +} // namespace crashpad + +#endif // CRASHPAD_SNAPSHOT_IOS_PROCESS_SNAPSHOT_IOS_H_ diff --git a/snapshot/ios/system_snapshot_ios.cc b/snapshot/ios/system_snapshot_ios.cc new file mode 100644 index 00000000..57c686e8 --- /dev/null +++ b/snapshot/ios/system_snapshot_ios.cc @@ -0,0 +1,224 @@ +// Copyright 2020 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "snapshot/ios/system_snapshot_ios.h" + +#include +#include +#include +#include +#include + +#include + +#include "base/logging.h" +#include "base/mac/mach_logging.h" +#include "base/strings/stringprintf.h" +#include "build/build_config.h" +#include "snapshot/cpu_context.h" +#include "snapshot/posix/timezone.h" +#include "util/mac/mac_util.h" +#include "util/numeric/in_range_cast.h" + +namespace crashpad { + +namespace internal { + +SystemSnapshotIOS::SystemSnapshotIOS() + : SystemSnapshot(), + os_version_build_(), + machine_description_(), + os_version_major_(0), + os_version_minor_(0), + os_version_bugfix_(0), + active_(0), + inactive_(0), + wired_(0), + free_(0), + cpu_count_(0), + cpu_vendor_(), + dst_status_(), + standard_offset_seconds_(0), + daylight_offset_seconds_(0), + standard_name_(), + daylight_name_(), + initialized_() {} + +SystemSnapshotIOS::~SystemSnapshotIOS() {} + +void SystemSnapshotIOS::Initialize(const IOSSystemDataCollector& system_data) { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + + system_data.OSVersion(&os_version_major_, + &os_version_minor_, + &os_version_bugfix_, + &os_version_build_); + machine_description_ = system_data.MachineDescription(); + cpu_count_ = system_data.ProcessorCount(); + cpu_vendor_ = system_data.CPUVendor(); + if (system_data.HasDaylightSavingTime()) { + dst_status_ = system_data.IsDaylightSavingTime() + ? SystemSnapshot::kObservingDaylightSavingTime + : SystemSnapshot::kObservingStandardTime; + } else { + dst_status_ = SystemSnapshot::kDoesNotObserveDaylightSavingTime; + } + standard_offset_seconds_ = system_data.StandardOffsetSeconds(); + daylight_offset_seconds_ = system_data.DaylightOffsetSeconds(); + standard_name_ = system_data.StandardName(); + daylight_name_ = system_data.DaylightName(); + + // Currently unused by minidump. + vm_size_t page_size; + host_page_size(mach_host_self(), &page_size); + mach_msg_type_number_t host_size = + sizeof(vm_statistics_data_t) / sizeof(integer_t); + vm_statistics_data_t vm_stat; + kern_return_t kr = host_statistics(mach_host_self(), + HOST_VM_INFO, + reinterpret_cast(&vm_stat), + &host_size); + if (kr != KERN_SUCCESS) { + MACH_LOG(WARNING, kr) << "host_statistics"; + } + active_ = vm_stat.active_count * page_size; + inactive_ = vm_stat.inactive_count * page_size; + wired_ = vm_stat.wire_count * page_size; + free_ = vm_stat.free_count * page_size; + + INITIALIZATION_STATE_SET_VALID(initialized_); +} + +CPUArchitecture SystemSnapshotIOS::GetCPUArchitecture() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); +#if defined(ARCH_CPU_X86_64) + return kCPUArchitectureX86_64; +#elif defined(ARCH_CPU_ARM64) + return kCPUArchitectureARM64; +#endif +} + +uint32_t SystemSnapshotIOS::CPURevision() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + // TODO(justincohen): sysctlbyname machdep.cpu.* returns -1 on iOS/ARM64, but + // consider recording this for X86_64 only. + return 0; +} + +uint8_t SystemSnapshotIOS::CPUCount() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return cpu_count_; +} + +std::string SystemSnapshotIOS::CPUVendor() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return cpu_vendor_; +} + +void SystemSnapshotIOS::CPUFrequency(uint64_t* current_hz, + uint64_t* max_hz) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + // TODO(justincohen): sysctlbyname hw.cpufrequency returns -1 on iOS/ARM64, + // but consider recording this for X86_64 only. + *current_hz = 0; + *max_hz = 0; +} + +uint32_t SystemSnapshotIOS::CPUX86Signature() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + // TODO(justincohen): Consider recording this for X86_64 only. + return 0; +} + +uint64_t SystemSnapshotIOS::CPUX86Features() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + // TODO(justincohen): Consider recording this for X86_64 only. + return 0; +} + +uint64_t SystemSnapshotIOS::CPUX86ExtendedFeatures() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + // TODO(justincohen): Consider recording this for X86_64 only. + return 0; +} + +uint32_t SystemSnapshotIOS::CPUX86Leaf7Features() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + // TODO(justincohen): Consider recording this for X86_64 only. + return 0; +} + +bool SystemSnapshotIOS::CPUX86SupportsDAZ() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + // TODO(justincohen): Consider recording this for X86_64 only. + return false; +} + +SystemSnapshot::OperatingSystem SystemSnapshotIOS::GetOperatingSystem() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return kOperatingSystemIOS; +} + +bool SystemSnapshotIOS::OSServer() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return false; +} + +void SystemSnapshotIOS::OSVersion(int* major, + int* minor, + int* bugfix, + std::string* build) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + *major = os_version_major_; + *minor = os_version_minor_; + *bugfix = os_version_bugfix_; + build->assign(os_version_build_); +} + +std::string SystemSnapshotIOS::OSVersionFull() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return base::StringPrintf("%d.%d.%d %s", + os_version_major_, + os_version_minor_, + os_version_bugfix_, + os_version_build_.c_str()); +} + +std::string SystemSnapshotIOS::MachineDescription() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return machine_description_; +} + +bool SystemSnapshotIOS::NXEnabled() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + // TODO(justincohen): Consider using kern.nx when available (pre-iOS 13, + // pre-OS X 10.15). Otherwise the bit is always enabled. + return true; +} + +void SystemSnapshotIOS::TimeZone(DaylightSavingTimeStatus* dst_status, + int* standard_offset_seconds, + int* daylight_offset_seconds, + std::string* standard_name, + std::string* daylight_name) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + *dst_status = dst_status_; + *standard_offset_seconds = standard_offset_seconds_; + *daylight_offset_seconds = daylight_offset_seconds_; + standard_name->assign(standard_name_); + daylight_name->assign(daylight_name_); +} + +} // namespace internal +} // namespace crashpad diff --git a/snapshot/ios/system_snapshot_ios.h b/snapshot/ios/system_snapshot_ios.h new file mode 100644 index 00000000..a38de4ef --- /dev/null +++ b/snapshot/ios/system_snapshot_ios.h @@ -0,0 +1,94 @@ +// Copyright 2020 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_SNAPSHOT_IOS_SYSTEM_SNAPSHOT_IOS_H_ +#define CRASHPAD_SNAPSHOT_IOS_SYSTEM_SNAPSHOT_IOS_H_ + +#include + +#include + +#include "base/macros.h" +#include "snapshot/system_snapshot.h" +#include "util/ios/ios_system_data_collector.h" +#include "util/misc/initialization_state_dcheck.h" + +namespace crashpad { + +namespace internal { + +//! \brief A SystemSnapshot of the running system, when the system runs iOS. +class SystemSnapshotIOS final : public SystemSnapshot { + public: + SystemSnapshotIOS(); + ~SystemSnapshotIOS() override; + + //! \brief Initializes the object. + //! + //! \param[in] system_data A class containing various system data points. + void Initialize(const IOSSystemDataCollector& system_data); + + // SystemSnapshot: + + CPUArchitecture GetCPUArchitecture() const override; + uint32_t CPURevision() const override; + uint8_t CPUCount() const override; + std::string CPUVendor() const override; + void CPUFrequency(uint64_t* current_hz, uint64_t* max_hz) const override; + uint32_t CPUX86Signature() const override; + uint64_t CPUX86Features() const override; + uint64_t CPUX86ExtendedFeatures() const override; + uint32_t CPUX86Leaf7Features() const override; + bool CPUX86SupportsDAZ() const override; + OperatingSystem GetOperatingSystem() const override; + bool OSServer() const override; + void OSVersion(int* major, + int* minor, + int* bugfix, + std::string* build) const override; + std::string OSVersionFull() const override; + bool NXEnabled() const override; + std::string MachineDescription() const override; + void TimeZone(DaylightSavingTimeStatus* dst_status, + int* standard_offset_seconds, + int* daylight_offset_seconds, + std::string* standard_name, + std::string* daylight_name) const override; + + private: + std::string os_version_build_; + std::string machine_description_; + int os_version_major_; + int os_version_minor_; + int os_version_bugfix_; + uint64_t active_; + uint64_t inactive_; + uint64_t wired_; + uint64_t free_; + int cpu_count_; + std::string cpu_vendor_; + DaylightSavingTimeStatus dst_status_; + int standard_offset_seconds_; + int daylight_offset_seconds_; + std::string standard_name_; + std::string daylight_name_; + InitializationStateDcheck initialized_; + + DISALLOW_COPY_AND_ASSIGN(SystemSnapshotIOS); +}; + +} // namespace internal +} // namespace crashpad + +#endif // CRASHPAD_SNAPSHOT_IOS_SYSTEM_SNAPSHOT_IOS_H_ diff --git a/snapshot/ios/thread_snapshot_ios.cc b/snapshot/ios/thread_snapshot_ios.cc new file mode 100644 index 00000000..a52be51e --- /dev/null +++ b/snapshot/ios/thread_snapshot_ios.cc @@ -0,0 +1,477 @@ +// Copyright 2020 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "snapshot/ios/thread_snapshot_ios.h" + +#include "base/mac/mach_logging.h" +#include "snapshot/mac/cpu_context_mac.h" + +namespace { + +#if defined(ARCH_CPU_X86_64) +const thread_state_flavor_t kThreadStateFlavor = x86_THREAD_STATE64; +const thread_state_flavor_t kFloatStateFlavor = x86_FLOAT_STATE64; +const thread_state_flavor_t kDebugStateFlavor = x86_DEBUG_STATE64; +#elif defined(ARCH_CPU_ARM64) +const thread_state_flavor_t kThreadStateFlavor = ARM_THREAD_STATE64; +const thread_state_flavor_t kFloatStateFlavor = ARM_NEON_STATE64; +#endif + +kern_return_t MachVMRegionRecurseDeepest(task_t task, + vm_address_t* address, + vm_size_t* size, + natural_t* depth, + vm_prot_t* protection, + unsigned int* user_tag) { + vm_region_submap_short_info_64 submap_info; + mach_msg_type_number_t count = VM_REGION_SUBMAP_SHORT_INFO_COUNT_64; + while (true) { + kern_return_t kr = vm_region_recurse_64( + task, + address, + size, + depth, + reinterpret_cast(&submap_info), + &count); + if (kr != KERN_SUCCESS) { + return kr; + } + + if (!submap_info.is_submap) { + *protection = submap_info.protection; + *user_tag = submap_info.user_tag; + return KERN_SUCCESS; + } + + ++*depth; + } +} + +//! \brief Adjusts the region for the red zone, if the ABI requires one. +//! +//! This method performs red zone calculation for CalculateStackRegion(). Its +//! parameters are local variables used within that method, and may be +//! modified as needed. +//! +//! Where a red zone is required, the region of memory captured for a thread’s +//! stack will be extended to include the red zone below the stack pointer, +//! provided that such memory is mapped, readable, and has the correct user +//! tag value. If these conditions cannot be met fully, as much of the red +//! zone will be captured as is possible while meeting these conditions. +//! +//! \param[in,out] start_address The base address of the region to begin +//! capturing stack memory from. On entry, \a start_address is the stack +//! pointer. On return, \a start_address may be decreased to encompass a +//! red zone. +//! \param[in,out] region_base The base address of the region that contains +//! stack memory. This is distinct from \a start_address in that \a +//! region_base will be page-aligned. On entry, \a region_base is the +//! base address of a region that contains \a start_address. On return, +//! if \a start_address is decremented and is outside of the region +//! originally described by \a region_base, \a region_base will also be +//! decremented appropriately. +//! \param[in,out] region_size The size of the region that contains stack +//! memory. This region begins at \a region_base. On return, if \a +//! region_base is decremented, \a region_size will be incremented +//! appropriately. +//! \param[in] user_tag The Mach VM system’s user tag for the region described +//! by the initial values of \a region_base and \a region_size. The red +//! zone will only be allowed to extend out of the region described by +//! these initial values if the user tag is appropriate for stack memory +//! and the expanded region has the same user tag value. +void LocateRedZone(vm_address_t* const start_address, + vm_address_t* const region_base, + vm_address_t* const region_size, + const unsigned int user_tag) { + // x86_64 has a red zone. See AMD64 ABI 0.99.8, + // https://raw.githubusercontent.com/wiki/hjl-tools/x86-psABI/x86-64-psABI-r252.pdf#page=19, + // section 3.2.2, “The Stack Frame”. + // So does ARM64, + // https://developer.apple.com/library/archive/documentation/Xcode/Conceptual/iPhoneOSABIReference/Articles/ARM64FunctionCallingConventions.html + // section "Red Zone". + constexpr vm_size_t kRedZoneSize = 128; + vm_address_t red_zone_base = + *start_address >= kRedZoneSize ? *start_address - kRedZoneSize : 0; + bool red_zone_ok = false; + if (red_zone_base >= *region_base) { + // The red zone is within the region already discovered. + red_zone_ok = true; + } else if (red_zone_base < *region_base && user_tag == VM_MEMORY_STACK) { + // Probe to see if there’s a region immediately below the one already + // discovered. + vm_address_t red_zone_region_base = red_zone_base; + vm_size_t red_zone_region_size; + natural_t red_zone_depth = 0; + vm_prot_t red_zone_protection; + unsigned int red_zone_user_tag; + kern_return_t kr = MachVMRegionRecurseDeepest(mach_task_self(), + &red_zone_region_base, + &red_zone_region_size, + &red_zone_depth, + &red_zone_protection, + &red_zone_user_tag); + if (kr != KERN_SUCCESS) { + MACH_LOG(INFO, kr) << "vm_region_recurse"; + *start_address = *region_base; + } else if (red_zone_region_base + red_zone_region_size == *region_base && + (red_zone_protection & VM_PROT_READ) != 0 && + red_zone_user_tag == user_tag) { + // The region containing the red zone is immediately below the region + // already found, it’s readable (not the guard region), and it has the + // same user tag as the region already found, so merge them. + red_zone_ok = true; + *region_base -= red_zone_region_size; + *region_size += red_zone_region_size; + } + } + + if (red_zone_ok) { + // Begin capturing from the base of the red zone (but not the entire + // region that encompasses the red zone). + *start_address = red_zone_base; + } else { + // The red zone would go lower into another region in memory, but no + // region was found. Memory can only be captured to an address as low as + // the base address of the region already found. + *start_address = *region_base; + } +} + +//! \brief Calculates the base address and size of the region used as a +//! thread’s stack. +//! +//! The region returned by this method may be formed by merging multiple +//! adjacent regions in a process’ memory map if appropriate. The base address +//! of the returned region may be lower than the \a stack_pointer passed in +//! when the ABI mandates a red zone below the stack pointer. +//! +//! \param[in] stack_pointer The stack pointer, referring to the top (lowest +//! address) of a thread’s stack. +//! \param[out] stack_region_size The size of the memory region used as the +//! thread’s stack. +//! +//! \return The base address (lowest address) of the memory region used as the +//! thread’s stack. +vm_address_t CalculateStackRegion(vm_address_t stack_pointer, + vm_size_t* stack_region_size) { + // For pthreads, it may be possible to compute the stack region based on the + // internal _pthread::stackaddr and _pthread::stacksize. The _pthread struct + // for a thread can be located at TSD slot 0, or the known offsets of + // stackaddr and stacksize from the TSD area could be used. + vm_address_t region_base = stack_pointer; + vm_size_t region_size; + natural_t depth = 0; + vm_prot_t protection; + unsigned int user_tag; + kern_return_t kr = MachVMRegionRecurseDeepest(mach_task_self(), + ®ion_base, + ®ion_size, + &depth, + &protection, + &user_tag); + if (kr != KERN_SUCCESS) { + MACH_LOG(INFO, kr) << "mach_vm_region_recurse"; + *stack_region_size = 0; + return 0; + } + + if (region_base > stack_pointer) { + // There’s nothing mapped at the stack pointer’s address. Something may have + // trashed the stack pointer. Note that this shouldn’t happen for a normal + // stack guard region violation because the guard region is mapped but has + // VM_PROT_NONE protection. + *stack_region_size = 0; + return 0; + } + + vm_address_t start_address = stack_pointer; + + if ((protection & VM_PROT_READ) == 0) { + // If the region isn’t readable, the stack pointer probably points to the + // guard region. Don’t include it as part of the stack, and don’t include + // anything at any lower memory address. The code below may still possibly + // find the real stack region at a memory address higher than this region. + start_address = region_base + region_size; + } else { + // If the ABI requires a red zone, adjust the region to include it if + // possible. + LocateRedZone(&start_address, ®ion_base, ®ion_size, user_tag); + + // Regardless of whether the ABI requires a red zone, capture up to + // kExtraCaptureSize additional bytes of stack, but only if present in the + // region that was already found. + constexpr vm_size_t kExtraCaptureSize = 128; + start_address = std::max(start_address >= kExtraCaptureSize + ? start_address - kExtraCaptureSize + : start_address, + region_base); + + // Align start_address to a 16-byte boundary, which can help readers by + // ensuring that data is aligned properly. This could page-align instead, + // but that might be wasteful. + constexpr vm_size_t kDesiredAlignment = 16; + start_address &= ~(kDesiredAlignment - 1); + DCHECK_GE(start_address, region_base); + } + + region_size -= (start_address - region_base); + region_base = start_address; + + vm_size_t total_region_size = region_size; + + // The stack region may have gotten split up into multiple abutting regions. + // Try to coalesce them. This frequently happens for the main thread’s stack + // when setrlimit(RLIMIT_STACK, …) is called. It may also happen if a region + // is split up due to an mprotect() or vm_protect() call. + // + // Stack regions created by the kernel and the pthreads library will be marked + // with the VM_MEMORY_STACK user tag. Scanning for multiple adjacent regions + // with the same tag should find an entire stack region. Checking that the + // protection on individual regions is not VM_PROT_NONE should guarantee that + // this algorithm doesn’t collect map entries belonging to another thread’s + // stack: well-behaved stacks (such as those created by the kernel and the + // pthreads library) have VM_PROT_NONE guard regions at their low-address + // ends. + // + // Other stack regions may not be so well-behaved and thus if user_tag is not + // VM_MEMORY_STACK, the single region that was found is used as-is without + // trying to merge it with other adjacent regions. + if (user_tag == VM_MEMORY_STACK) { + vm_address_t try_address = region_base; + vm_address_t original_try_address; + + while (try_address += region_size, + original_try_address = try_address, + (kr = MachVMRegionRecurseDeepest(mach_task_self(), + &try_address, + ®ion_size, + &depth, + &protection, + &user_tag) == KERN_SUCCESS) && + try_address == original_try_address && + (protection & VM_PROT_READ) != 0 && + user_tag == VM_MEMORY_STACK) { + total_region_size += region_size; + } + + if (kr != KERN_SUCCESS && kr != KERN_INVALID_ADDRESS) { + // Tolerate KERN_INVALID_ADDRESS because it will be returned when there + // are no more regions in the map at or above the specified |try_address|. + MACH_LOG(INFO, kr) << "vm_region_recurse"; + } + } + + *stack_region_size = total_region_size; + return region_base; +} + +} // namespace + +namespace crashpad { +namespace internal { + +ThreadSnapshotIOS::ThreadSnapshotIOS() + : ThreadSnapshot(), + context_(), + stack_(), + thread_id_(0), + thread_specific_data_address_(0), + suspend_count_(0), + priority_(0), + initialized_() {} + +ThreadSnapshotIOS::~ThreadSnapshotIOS() {} + +// static +thread_act_array_t ThreadSnapshotIOS::GetThreads( + mach_msg_type_number_t* count) { + thread_act_array_t threads; + kern_return_t kr = task_threads(mach_task_self(), &threads, count); + if (kr != KERN_SUCCESS) { + MACH_LOG(WARNING, kr) << "task_threads"; + } + return threads; +} + +bool ThreadSnapshotIOS::Initialize(thread_t thread) { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + + // TODO(justincohen): Move the following thread_get_state, thread_get_info, + // thread_policy_get and CalculateStackRegion to the serialize-on-read + // section. + thread_basic_info basic_info; + thread_precedence_policy precedence; + vm_size_t stack_region_size; + vm_address_t stack_region_address; +#if defined(ARCH_CPU_X86_64) + x86_thread_state64_t thread_state; + x86_float_state64_t float_state; + x86_debug_state64_t debug_state; + mach_msg_type_number_t thread_state_count = x86_THREAD_STATE64_COUNT; + mach_msg_type_number_t float_state_count = x86_FLOAT_STATE64_COUNT; + mach_msg_type_number_t debug_state_count = x86_DEBUG_STATE64_COUNT; +#elif defined(ARCH_CPU_ARM64) + arm_thread_state64_t thread_state; + arm_neon_state64_t float_state; + mach_msg_type_number_t thread_state_count = ARM_THREAD_STATE64_COUNT; + mach_msg_type_number_t float_state_count = ARM_NEON_STATE64_COUNT; +#endif + + kern_return_t kr = + thread_get_state(thread, + kThreadStateFlavor, + reinterpret_cast(&thread_state), + &thread_state_count); + if (kr != KERN_SUCCESS) { + MACH_LOG(ERROR, kr) << "thread_get_state(" << kThreadStateFlavor << ")"; + } + + kr = thread_get_state(thread, + kFloatStateFlavor, + reinterpret_cast(&float_state), + &float_state_count); + if (kr != KERN_SUCCESS) { + MACH_LOG(ERROR, kr) << "thread_get_state(" << kFloatStateFlavor << ")"; + } + +#if defined(ARCH_CPU_X86_64) + kr = thread_get_state(thread, + kDebugStateFlavor, + reinterpret_cast(&debug_state), + &debug_state_count); + if (kr != KERN_SUCCESS) { + MACH_LOG(ERROR, kr) << "thread_get_state(" << kDebugStateFlavor << ")"; + } +#endif + + mach_msg_type_number_t count = THREAD_BASIC_INFO_COUNT; + kr = thread_info(thread, + THREAD_BASIC_INFO, + reinterpret_cast(&basic_info), + &count); + if (kr != KERN_SUCCESS) { + MACH_LOG(WARNING, kr) << "thread_info(THREAD_BASIC_INFO)"; + } + + thread_identifier_info identifier_info; + count = THREAD_IDENTIFIER_INFO_COUNT; + kr = thread_info(thread, + THREAD_IDENTIFIER_INFO, + reinterpret_cast(&identifier_info), + &count); + if (kr != KERN_SUCCESS) { + MACH_LOG(WARNING, kr) << "thread_info(THREAD_IDENTIFIER_INFO)"; + } + + count = THREAD_PRECEDENCE_POLICY_COUNT; + boolean_t get_default = FALSE; + kr = thread_policy_get(thread, + THREAD_PRECEDENCE_POLICY, + reinterpret_cast(&precedence), + &count, + &get_default); + if (kr != KERN_SUCCESS) { + MACH_LOG(ERROR, kr) << "thread_policy_get"; + } + +#if defined(ARCH_CPU_X86_64) + vm_address_t stack_pointer = thread_state.__rsp; +#elif defined(ARCH_CPU_ARM64) + vm_address_t stack_pointer = thread_state.__sp; +#endif + stack_region_address = + CalculateStackRegion(stack_pointer, &stack_region_size); + + // TODO(justincohen): Assume the following will fill in snapshot data from + // a deserialized object. + thread_id_ = identifier_info.thread_id; + suspend_count_ = basic_info.suspend_count; + priority_ = precedence.importance; + + // thread_identifier_info::thread_handle contains the base of the + // thread-specific data area, which on x86 and x86_64 is the thread’s base + // address of the %gs segment. 10.9.2 xnu-2422.90.20/osfmk/kern/thread.c + // thread_info_internal() gets the value from + // machine_thread::cthread_self, which is the same value used to set the + // %gs base in xnu-2422.90.20/osfmk/i386/pcb_native.c + // act_machine_switch_pcb(). + // + // On ARM64 10.15.0 xnu-6153.11.26/osfmk/kern/thread.c, it sets + // thread_identifier_info_t::thread_handle to + // thread->machine.cthread_self, which is set to tsd_base in + // osfmk/arm64/pcb.c. + thread_specific_data_address_ = identifier_info.thread_handle; + stack_.Initialize(stack_region_address, stack_region_size); + +#if defined(ARCH_CPU_X86_64) + context_.architecture = kCPUArchitectureX86_64; + context_.x86_64 = &context_x86_64_; + InitializeCPUContextX86_64(&context_x86_64_, + THREAD_STATE_NONE, + nullptr, + 0, + &thread_state, + &float_state, + &debug_state); +#elif defined(ARCH_CPU_ARM64) + context_.architecture = kCPUArchitectureARM64; + context_.arm64 = &context_arm64_; + InitializeCPUContextARM64(&context_arm64_, + THREAD_STATE_NONE, + nullptr, + 0, + &thread_state, + &float_state); +#endif + + INITIALIZATION_STATE_SET_VALID(initialized_); + return true; +} + +const CPUContext* ThreadSnapshotIOS::Context() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return &context_; +} + +const MemorySnapshot* ThreadSnapshotIOS::Stack() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return &stack_; +} + +uint64_t ThreadSnapshotIOS::ThreadID() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return thread_id_; +} + +int ThreadSnapshotIOS::SuspendCount() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return suspend_count_; +} + +int ThreadSnapshotIOS::Priority() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return priority_; +} + +uint64_t ThreadSnapshotIOS::ThreadSpecificDataAddress() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return thread_specific_data_address_; +} + +std::vector ThreadSnapshotIOS::ExtraMemory() const { + return std::vector(); +} + +} // namespace internal +} // namespace crashpad diff --git a/snapshot/ios/thread_snapshot_ios.h b/snapshot/ios/thread_snapshot_ios.h new file mode 100644 index 00000000..978a8186 --- /dev/null +++ b/snapshot/ios/thread_snapshot_ios.h @@ -0,0 +1,77 @@ +// Copyright 2020 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_SNAPSHOT_IOS_THREAD_SNAPSHOT_IOS_H_ +#define CRASHPAD_SNAPSHOT_IOS_THREAD_SNAPSHOT_IOS_H_ + +#include "base/macros.h" +#include "build/build_config.h" +#include "snapshot/cpu_context.h" +#include "snapshot/ios/memory_snapshot_ios.h" +#include "snapshot/thread_snapshot.h" +#include "util/misc/initialization_state_dcheck.h" + +namespace crashpad { +namespace internal { + +//! \brief A ThreadSnapshot of a thread on an iOS system. +class ThreadSnapshotIOS final : public ThreadSnapshot { + public: + ThreadSnapshotIOS(); + ~ThreadSnapshotIOS() override; + + //! \brief Initializes the object. + //! + //! \brief thread The Mach thread used to initialize this object. + bool Initialize(thread_t thread); + + //! \brief Returns an array of thread_t threads. + //! + //! \param[out] count The number of threads returned. + //! + //! \return An array of of size \a count threads. + static thread_act_array_t GetThreads(mach_msg_type_number_t* count); + + // ThreadSnapshot: + const CPUContext* Context() const override; + const MemorySnapshot* Stack() const override; + uint64_t ThreadID() const override; + int SuspendCount() const override; + int Priority() const override; + uint64_t ThreadSpecificDataAddress() const override; + std::vector ExtraMemory() const override; + + private: +#if defined(ARCH_CPU_X86_64) + CPUContextX86_64 context_x86_64_; +#elif defined(ARCH_CPU_ARM64) + CPUContextARM64 context_arm64_; +#else +#error Port. +#endif // ARCH_CPU_X86_64 + CPUContext context_; + MemorySnapshotIOS stack_; + uint64_t thread_id_; + uint64_t thread_specific_data_address_; + int suspend_count_; + int priority_; + InitializationStateDcheck initialized_; + + DISALLOW_COPY_AND_ASSIGN(ThreadSnapshotIOS); +}; + +} // namespace internal +} // namespace crashpad + +#endif // CRASHPAD_SNAPSHOT_IOS_THREAD_SNAPSHOT_IOS_H_ diff --git a/snapshot/linux/cpu_context_linux.cc b/snapshot/linux/cpu_context_linux.cc index ebf9d7e9..8464a5a2 100644 --- a/snapshot/linux/cpu_context_linux.cc +++ b/snapshot/linux/cpu_context_linux.cc @@ -17,6 +17,8 @@ #include #include +#include + #include "base/logging.h" namespace crashpad { @@ -62,7 +64,6 @@ void InitializeCPUContextX86(const ThreadContext::t32_t& thread_context, context->dr5 = 0; context->dr6 = 0; context->dr7 = 0; - } void InitializeCPUContextX86(const SignalThreadContext32& thread_context, @@ -241,7 +242,14 @@ void InitializeCPUContextARM64_NoFloatingPoint( memcpy(context->regs, thread_context.regs, sizeof(context->regs)); context->sp = thread_context.sp; context->pc = thread_context.pc; - context->pstate = thread_context.pstate; + // Linux seems to only be putting the SPSR register in its "pstate" field. + // https://elixir.bootlin.com/linux/latest/source/arch/arm64/include/uapi/asm/ptrace.h + if (thread_context.pstate > + std::numeric_limitsspsr)>::max()) { + LOG(WARNING) << "pstate truncation: we only expect the SPSR bits to be set " + "in the pstate"; + } + context->spsr = static_castspsr)>(thread_context.pstate); memset(&context->fpsimd, 0, sizeof(context->fpsimd)); context->fpsr = 0; diff --git a/snapshot/linux/cpu_context_linux.h b/snapshot/linux/cpu_context_linux.h index 37fbc432..9f46a489 100644 --- a/snapshot/linux/cpu_context_linux.h +++ b/snapshot/linux/cpu_context_linux.h @@ -170,7 +170,7 @@ void InitializeCPUContextMIPS( memcpy(&context->fpregs, &float_context.fpregs, sizeof(context->fpregs)); context->fpcsr = float_context.fpcsr; context->fir = float_context.fpu_id; -}; +} #endif // ARCH_CPU_MIPS_FAMILY || DOXYGEN diff --git a/snapshot/linux/debug_rendezvous_test.cc b/snapshot/linux/debug_rendezvous_test.cc index 431e2bbe..be22c903 100644 --- a/snapshot/linux/debug_rendezvous_test.cc +++ b/snapshot/linux/debug_rendezvous_test.cc @@ -37,28 +37,13 @@ #include "util/process/process_memory_range.h" #if defined(OS_ANDROID) -#include +#include #endif namespace crashpad { namespace test { namespace { -#if defined(OS_ANDROID) -int AndroidRuntimeAPI() { - char api_string[PROP_VALUE_MAX]; - int length = __system_property_get("ro.build.version.sdk", api_string); - if (length <= 0) { - return -1; - } - - int api_level; - bool success = - base::StringToInt(base::StringPiece(api_string, length), &api_level); - return success ? api_level : -1; -} -#endif // OS_ANDROID - void TestAgainstTarget(PtraceConnection* connection) { // Use ElfImageReader on the main executable which can tell us the debug // address. glibc declares the symbol _r_debug in link.h which we can use to @@ -74,10 +59,10 @@ void TestAgainstTarget(PtraceConnection* connection) { const MemoryMap::Mapping* phdr_mapping = mappings.FindMapping(phdrs); ASSERT_TRUE(phdr_mapping); - std::vector exe_mappings = - mappings.FindFilePossibleMmapStarts(*phdr_mapping); - ASSERT_EQ(exe_mappings.size(), 1u); - LinuxVMAddress elf_address = exe_mappings[0]->range.Base(); + + auto exe_mappings = mappings.FindFilePossibleMmapStarts(*phdr_mapping); + ASSERT_EQ(exe_mappings->Count(), 1u); + LinuxVMAddress elf_address = exe_mappings->Next()->range.Base(); ProcessMemoryLinux memory; ASSERT_TRUE(memory.Initialize(connection->GetProcessID())); @@ -94,7 +79,7 @@ void TestAgainstTarget(PtraceConnection* connection) { ASSERT_TRUE(debug.Initialize(range, debug_address)); #if defined(OS_ANDROID) - const int android_runtime_api = AndroidRuntimeAPI(); + const int android_runtime_api = android_get_device_api_level(); ASSERT_GE(android_runtime_api, 1); EXPECT_NE(debug.Executable()->name.find("crashpad_snapshot_test"), @@ -143,13 +128,13 @@ void TestAgainstTarget(PtraceConnection* connection) { mappings.FindMapping(module.dynamic_array); ASSERT_TRUE(dyn_mapping); - std::vector possible_mappings = - mappings.FindFilePossibleMmapStarts(*dyn_mapping); - ASSERT_GE(possible_mappings.size(), 1u); + auto possible_mappings = mappings.FindFilePossibleMmapStarts(*dyn_mapping); + ASSERT_GE(possible_mappings->Count(), 1u); std::unique_ptr module_reader; const MemoryMap::Mapping* module_mapping = nullptr; - for (const auto mapping : possible_mappings) { + const MemoryMap::Mapping* mapping = nullptr; + while ((mapping = possible_mappings->Next())) { auto parsed_module = std::make_unique(); VMAddress dynamic_address; if (parsed_module->Initialize(range, mapping->range.Base()) && diff --git a/snapshot/linux/exception_snapshot_linux.cc b/snapshot/linux/exception_snapshot_linux.cc index 4256f942..cd40b3b1 100644 --- a/snapshot/linux/exception_snapshot_linux.cc +++ b/snapshot/linux/exception_snapshot_linux.cc @@ -126,7 +126,7 @@ bool ExceptionSnapshotLinux::ReadContext( context_.arm = &context_union_.arm; CPUContextARM* dest_context = context_.arm; - ProcessMemory* memory = reader->Memory(); + const ProcessMemory* memory = reader->Memory(); LinuxVMAddress gprs_address = context_address + offsetof(UContext, mcontext32) + @@ -203,7 +203,7 @@ bool ExceptionSnapshotLinux::ReadContext( context_.arm64 = &context_union_.arm64; CPUContextARM64* dest_context = context_.arm64; - ProcessMemory* memory = reader->Memory(); + const ProcessMemory* memory = reader->Memory(); LinuxVMAddress gprs_address = context_address + offsetof(UContext, mcontext64) + @@ -274,7 +274,7 @@ template static bool ReadContext(ProcessReaderLinux* reader, LinuxVMAddress context_address, typename Traits::CPUContext* dest_context) { - ProcessMemory* memory = reader->Memory(); + const ProcessMemory* memory = reader->Memory(); LinuxVMAddress gregs_address = context_address + offsetof(UContext, mcontext) + diff --git a/snapshot/linux/exception_snapshot_linux_test.cc b/snapshot/linux/exception_snapshot_linux_test.cc index df9ad9e9..e4ff1ab7 100644 --- a/snapshot/linux/exception_snapshot_linux_test.cc +++ b/snapshot/linux/exception_snapshot_linux_test.cc @@ -23,6 +23,7 @@ #include "base/bit_cast.h" #include "base/macros.h" +#include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "gtest/gtest.h" #include "snapshot/cpu_architecture.h" @@ -170,7 +171,7 @@ void InitializeContext(NativeCPUContext* 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); + for (size_t reg = 0; reg < base::size(test_context->vfp.context.vfp.fpregs); ++reg) { test_context->vfp.context.vfp.fpregs[reg] = reg; } @@ -218,7 +219,7 @@ struct TestCoprocessorContext { void InitializeContext(NativeCPUContext* context) { memset(context, 'x', sizeof(*context)); - for (size_t index = 0; index < arraysize(context->uc_mcontext.regs); + for (size_t index = 0; index < base::size(context->uc_mcontext.regs); ++index) { context->uc_mcontext.regs[index] = index; } @@ -237,7 +238,7 @@ void InitializeContext(NativeCPUContext* context) { test_context->fpsimd.head.size = sizeof(test_context->fpsimd); test_context->fpsimd.fpsr = 1; test_context->fpsimd.fpcr = 2; - for (size_t reg = 0; reg < arraysize(test_context->fpsimd.vregs); ++reg) { + for (size_t reg = 0; reg < base::size(test_context->fpsimd.vregs); ++reg) { test_context->fpsimd.vregs[reg] = reg; } @@ -254,7 +255,7 @@ void ExpectContext(const CPUContext& actual, const NativeCPUContext& expected) { 0); EXPECT_EQ(actual.arm64->sp, expected.uc_mcontext.sp); EXPECT_EQ(actual.arm64->pc, expected.uc_mcontext.pc); - EXPECT_EQ(actual.arm64->pstate, expected.uc_mcontext.pstate); + EXPECT_EQ(actual.arm64->spsr, expected.uc_mcontext.pstate); auto test_context = reinterpret_cast( expected.uc_mcontext.__reserved); @@ -270,7 +271,7 @@ void ExpectContext(const CPUContext& actual, const NativeCPUContext& expected) { using NativeCPUContext = ucontext_t; void InitializeContext(NativeCPUContext* context) { - for (size_t reg = 0; reg < arraysize(context->uc_mcontext.gregs); ++reg) { + for (size_t reg = 0; reg < base::size(context->uc_mcontext.gregs); ++reg) { context->uc_mcontext.gregs[reg] = reg; } memset(&context->uc_mcontext.fpregs, 44, sizeof(context->uc_mcontext.fpregs)); @@ -285,7 +286,7 @@ void ExpectContext(const CPUContext& actual, const NativeCPUContext& expected) { #define CPU_ARCH_NAME mips64 #endif - for (size_t reg = 0; reg < arraysize(expected.uc_mcontext.gregs); ++reg) { + for (size_t reg = 0; reg < base::size(expected.uc_mcontext.gregs); ++reg) { EXPECT_EQ(actual.CPU_ARCH_NAME->regs[reg], expected.uc_mcontext.gregs[reg]); } diff --git a/snapshot/linux/process_reader_linux.cc b/snapshot/linux/process_reader_linux.cc index 33d4f92b..b96abfe7 100644 --- a/snapshot/linux/process_reader_linux.cc +++ b/snapshot/linux/process_reader_linux.cc @@ -17,7 +17,6 @@ #include #include #include -#include #include #include #include @@ -25,13 +24,14 @@ #include #include "base/logging.h" -#include "base/strings/string_number_conversions.h" #include "build/build_config.h" #include "snapshot/linux/debug_rendezvous.h" -#include "util/file/directory_reader.h" #include "util/linux/auxiliary_vector.h" #include "util/linux/proc_stat_reader.h" -#include "util/misc/as_underlying_type.h" + +#if defined(OS_ANDROID) +#include +#endif namespace crashpad { @@ -64,31 +64,36 @@ bool ProcessReaderLinux::Thread::InitializePtrace( return false; } + // TODO(jperaza): Collect scheduling priorities via the broker when they can't + // be collected directly. + have_priorities = false; + // TODO(jperaza): Starting with Linux 3.14, scheduling policy, static // priority, and nice value can be collected all in one call with // sched_getattr(). int res = sched_getscheduler(tid); if (res < 0) { - PLOG(ERROR) << "sched_getscheduler"; - return false; + PLOG(WARNING) << "sched_getscheduler"; + return true; } sched_policy = res; sched_param param; if (sched_getparam(tid, ¶m) != 0) { - PLOG(ERROR) << "sched_getparam"; - return false; + PLOG(WARNING) << "sched_getparam"; + return true; } static_priority = param.sched_priority; errno = 0; res = getpriority(PRIO_PROCESS, tid); if (res == -1 && errno) { - PLOG(ERROR) << "getpriority"; - return false; + PLOG(WARNING) << "getpriority"; + return true; } nice_value = res; + have_priorities = true; return true; } @@ -236,7 +241,7 @@ bool ProcessReaderLinux::CPUTimes(timeval* user_time, for (const Thread& thread : threads_) { ProcStatReader stat; - if (!stat.Initialize(thread.tid)) { + if (!stat.Initialize(connection_, thread.tid)) { return false; } @@ -275,6 +280,81 @@ const std::vector& ProcessReaderLinux::Modules() { return modules_; } +void ProcessReaderLinux::InitializeAbortMessage() { +#if defined(OS_ANDROID) + const MemoryMap::Mapping* mapping = + memory_map_.FindMappingWithName("[anon:abort message]"); + if (!mapping) { + return; + } + + if (is_64_bit_) { + ReadAbortMessage(mapping); + } else { + ReadAbortMessage(mapping); + } +#endif +} + +#if defined(OS_ANDROID) + +// These structure definitions and the magic numbers below were copied from +// bionic/libc/bionic/android_set_abort_message.cpp + +template +struct abort_msg_t { + uint32_t size; + char msg[0]; +}; + +template <> +struct abort_msg_t { + uint64_t size; + char msg[0]; +}; + +template +struct magic_abort_msg_t { + uint64_t magic1; + uint64_t magic2; + abort_msg_t msg; +}; + +template +void ProcessReaderLinux::ReadAbortMessage(const MemoryMap::Mapping* mapping) { + magic_abort_msg_t header; + if (!Memory()->Read( + mapping->range.Base(), sizeof(magic_abort_msg_t), &header)) { + return; + } + + size_t size = header.msg.size - sizeof(magic_abort_msg_t) - 1; + if (header.magic1 != 0xb18e40886ac388f0ULL || + header.magic2 != 0xc6dfba755a1de0b5ULL || + mapping->range.Size() < + offsetof(magic_abort_msg_t, msg.msg) + size) { + return; + } + + abort_message_.resize(size); + if (!Memory()->Read( + mapping->range.Base() + offsetof(magic_abort_msg_t, msg.msg), + size, + &abort_message_[0])) { + abort_message_.clear(); + } +} + +#endif // OS_ANDROID + +const std::string& ProcessReaderLinux::AbortMessage() { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + if (abort_message_.empty()) { + InitializeAbortMessage(); + } + return abort_message_; +} + void ProcessReaderLinux::InitializeThreads() { DCHECK(threads_.empty()); initialized_threads_ = true; @@ -298,23 +378,11 @@ void ProcessReaderLinux::InitializeThreads() { LOG(WARNING) << "Couldn't initialize main thread."; } - char path[32]; - snprintf(path, arraysize(path), "/proc/%d/task", pid); bool main_thread_found = false; - DirectoryReader reader; - if (!reader.Open(base::FilePath(path))) { - return; - } - base::FilePath tid_str; - DirectoryReader::Result result; - while ((result = reader.NextFile(&tid_str)) == - DirectoryReader::Result::kSuccess) { - pid_t tid; - if (!base::StringToInt(tid_str.value(), &tid)) { - LOG(ERROR) << "format error"; - continue; - } - + std::vector thread_ids; + bool result = connection_->Threads(&thread_ids); + DCHECK(result); + for (pid_t tid : thread_ids) { if (tid == pid) { DCHECK(!main_thread_found); main_thread_found = true; @@ -328,8 +396,6 @@ void ProcessReaderLinux::InitializeThreads() { threads_.push_back(thread); } } - DCHECK_EQ(AsUnderlyingType(result), - AsUnderlyingType(DirectoryReader::Result::kNoMoreFiles)); DCHECK(main_thread_found); } @@ -365,17 +431,15 @@ void ProcessReaderLinux::InitializeModules() { return; } - std::vector possible_mappings = + auto possible_mappings = memory_map_.FindFilePossibleMmapStarts(*phdr_mapping); - for (auto riter = possible_mappings.rbegin(); - riter != possible_mappings.rend(); - ++riter) { - auto mapping = *riter; + const MemoryMap::Mapping* mapping = nullptr; + while ((mapping = possible_mappings->Next())) { auto parsed_exe = std::make_unique(); if (parsed_exe->Initialize( range, mapping->range.Base(), - /* verbose= */ possible_mappings.size() == 1) && + /* verbose= */ possible_mappings->Count() == 1) && parsed_exe->GetProgramHeaderTableAddress() == phdrs) { exe_mapping = mapping; exe_reader = std::move(parsed_exe); @@ -383,7 +447,8 @@ void ProcessReaderLinux::InitializeModules() { } } if (!exe_mapping) { - LOG(ERROR) << "no exe mappings " << possible_mappings.size(); + LOG(ERROR) << "no exe mappings 0x" << std::hex + << phdr_mapping->range.Base(); return; } } @@ -420,18 +485,30 @@ void ProcessReaderLinux::InitializeModules() { continue; } - std::vector possible_mappings = +#if defined(OS_ANDROID) + // Beginning at API 21, Bionic provides android_dlopen_ext() which allows + // passing a file descriptor with an existing relro segment to the loader. + // This means that the mapping attributes of dyn_mapping may be unrelated + // to the attributes of the other mappings for the module. In this case, + // search all mappings in reverse order from dyn_mapping until a module is + // parsed whose dynamic address matches the value in the debug link. + static int api_level = android_get_device_api_level(); + auto possible_mappings = + (api_level >= 21 || api_level < 0) + ? memory_map_.ReverseIteratorFrom(*dyn_mapping) + : memory_map_.FindFilePossibleMmapStarts(*dyn_mapping); +#else + auto possible_mappings = memory_map_.FindFilePossibleMmapStarts(*dyn_mapping); - for (auto riter = possible_mappings.rbegin(); - riter != possible_mappings.rend(); - ++riter) { - auto mapping = *riter; +#endif + const MemoryMap::Mapping* mapping = nullptr; + while ((mapping = possible_mappings->Next())) { auto parsed_module = std::make_unique(); VMAddress dynamic_address; if (parsed_module->Initialize( range, mapping->range.Base(), - /* verbose= */ possible_mappings.size() == 1) && + /* verbose= */ possible_mappings->Count() == 1) && parsed_module->GetDynamicArrayAddress(&dynamic_address) && dynamic_address == entry.dynamic_array) { module_mapping = mapping; @@ -440,13 +517,19 @@ void ProcessReaderLinux::InitializeModules() { } } if (!module_mapping) { - LOG(ERROR) << "no module mappings " << possible_mappings.size(); + LOG(ERROR) << "no module mappings 0x" << std::hex + << dyn_mapping->range.Base(); continue; } } Module module = {}; - module.name = !entry.name.empty() ? entry.name : module_mapping->name; + std::string soname; + if (elf_reader->SoName(&soname) && !soname.empty()) { + module.name = soname; + } else { + module.name = !entry.name.empty() ? entry.name : module_mapping->name; + } module.elf_reader = elf_reader.get(); module.type = loader_base && elf_reader->Address() == loader_base ? ModuleSnapshot::kModuleTypeDynamicLoader diff --git a/snapshot/linux/process_reader_linux.h b/snapshot/linux/process_reader_linux.h index 7c17d9d1..258e102a 100644 --- a/snapshot/linux/process_reader_linux.h +++ b/snapshot/linux/process_reader_linux.h @@ -66,6 +66,10 @@ class ProcessReaderLinux { int static_priority; int nice_value; + //! \brief `true` if `sched_policy`, `static_priority`, and `nice_value` are + //! all valid. + bool have_priorities; + private: friend class ProcessReaderLinux; @@ -116,7 +120,7 @@ class ProcessReaderLinux { pid_t ParentProcessID() const { return process_info_.ParentProcessID(); } //! \brief Return a memory reader for the target process. - ProcessMemory* Memory() { return connection_->Memory(); } + const ProcessMemory* Memory() const { return connection_->Memory(); } //! \brief Return a memory map of the target process. MemoryMap* GetMemoryMap() { return &memory_map_; } @@ -149,15 +153,23 @@ class ProcessReaderLinux { //! `0`) corresponds to the main executable. const std::vector& Modules(); + //! \return On Android, the abort message that was passed to + //! android_set_abort_message(). This is only available on Q or later. + const std::string& AbortMessage(); + private: void InitializeThreads(); void InitializeModules(); + void InitializeAbortMessage(); + template + void ReadAbortMessage(const MemoryMap::Mapping* mapping); PtraceConnection* connection_; // weak ProcessInfo process_info_; MemoryMap memory_map_; std::vector threads_; std::vector modules_; + std::string abort_message_; std::vector> elf_readers_; bool is_64_bit_; bool initialized_threads_; diff --git a/snapshot/linux/process_reader_linux_test.cc b/snapshot/linux/process_reader_linux_test.cc index d827f35d..5b572361 100644 --- a/snapshot/linux/process_reader_linux_test.cc +++ b/snapshot/linux/process_reader_linux_test.cc @@ -33,6 +33,7 @@ #include "base/format_macros.h" #include "base/memory/free_deleter.h" +#include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "build/build_config.h" #include "gtest/gtest.h" @@ -48,10 +49,17 @@ #include "util/linux/direct_ptrace_connection.h" #include "util/misc/address_sanitizer.h" #include "util/misc/from_pointer_cast.h" +#include "util/misc/memory_sanitizer.h" #include "util/synchronization/semaphore.h" #if defined(OS_ANDROID) #include +#include +#include "dlfcn_internal.h" + +// Normally this comes from set_abort_message.h, but only at API level 21. +extern "C" void android_set_abort_message(const char* msg) + __attribute__((weak)); #endif namespace crashpad { @@ -79,12 +87,14 @@ TEST(ProcessReaderLinux, SelfBasic) { EXPECT_EQ(process_reader.ParentProcessID(), getppid()); static constexpr char kTestMemory[] = "Some test memory"; - char buffer[arraysize(kTestMemory)]; + char buffer[base::size(kTestMemory)]; ASSERT_TRUE(process_reader.Memory()->Read( reinterpret_cast(kTestMemory), sizeof(kTestMemory), &buffer)); EXPECT_STREQ(kTestMemory, buffer); + + EXPECT_EQ("", process_reader.AbortMessage()); } constexpr char kTestMemory[] = "Read me from another process"; @@ -327,6 +337,11 @@ class ChildThreadTest : public Multiprocess { thread_pool.StartThreads(kThreadCount, stack_size_); TestThreadPool::ThreadExpectation expectation; +#if defined(MEMORY_SANITIZER) + // memset() + re-initialization is required to zero padding bytes for MSan. + memset(&expectation, 0, sizeof(expectation)); +#endif // defined(MEMORY_SANITIZER) + expectation = {}; expectation.tls = GetTLS(); expectation.stack_address = reinterpret_cast(&thread_pool); @@ -412,7 +427,7 @@ class ChildWithSplitStackTest : public Multiprocess { } void MultiprocessChild() override { - const LinuxVMSize stack_size = page_size_ * 3; + const LinuxVMSize stack_size = page_size_ * 4; GrowStack(stack_size, reinterpret_cast(&stack_size)); } @@ -425,7 +440,7 @@ class ChildWithSplitStackTest : public Multiprocess { } else { // Write-protect a page on our stack to split up the mapping LinuxVMAddress page_addr = - stack_address - (stack_address % page_size_) + page_size_; + stack_address - (stack_address % page_size_) + 2 * page_size_; ASSERT_EQ( mprotect(reinterpret_cast(page_addr), page_size_, PROT_READ), 0) @@ -453,7 +468,14 @@ class ChildWithSplitStackTest : public Multiprocess { DISALLOW_COPY_AND_ASSIGN(ChildWithSplitStackTest); }; -TEST(ProcessReaderLinux, ChildWithSplitStack) { +// AddressSanitizer with use-after-return detection causes stack variables to +// be allocated on the heap. +#if defined(ADDRESS_SANITIZER) +#define MAYBE_ChildWithSplitStack DISABLED_ChildWithSplitStack +#else +#define MAYBE_ChildWithSplitStack ChildWithSplitStack +#endif +TEST(ProcessReaderLinux, MAYBE_ChildWithSplitStack) { ChildWithSplitStackTest test; test.Run(); } @@ -715,7 +737,7 @@ void ExpectTestModule(ProcessReaderLinux* reader, auto dynamic_mapping = reader->GetMemoryMap()->FindMapping(dynamic_addr); auto mappings = reader->GetMemoryMap()->FindFilePossibleMmapStarts(*dynamic_mapping); - EXPECT_EQ(mappings.size(), 2u); + EXPECT_EQ(mappings->Count(), 2u); return; } } @@ -761,7 +783,7 @@ class ChildModuleTest : public Multiprocess { ScopedModuleHandle empty_test_module(LoadTestModule(module_name_)); ASSERT_TRUE(empty_test_module.valid()); - char c; + char c = 0; ASSERT_TRUE(LoggingWriteFile(WritePipeHandle(), &c, sizeof(c))); CheckedReadFileAtEOF(ReadPipeHandle()); @@ -777,6 +799,30 @@ TEST(ProcessReaderLinux, ChildModules) { test.Run(); } +#if defined(OS_ANDROID) +const char kTestAbortMessage[] = "test abort message"; + +TEST(ProcessReaderLinux, AbortMessage) { + // This test requires Q. The API level on Q devices will be 28 until the API + // is finalized, so we can't check API level yet. For now, test for the + // presence of a libc symbol which was introduced in Q. + if (!crashpad::internal::Dlsym(RTLD_DEFAULT, + "android_fdsan_close_with_tag")) { + GTEST_SKIP(); + } + + android_set_abort_message(kTestAbortMessage); + + FakePtraceConnection connection; + connection.Initialize(getpid()); + + ProcessReaderLinux process_reader; + ASSERT_TRUE(process_reader.Initialize(&connection)); + + EXPECT_EQ(kTestAbortMessage, process_reader.AbortMessage()); +} +#endif + } // namespace } // namespace test } // namespace crashpad diff --git a/snapshot/linux/process_snapshot_linux.cc b/snapshot/linux/process_snapshot_linux.cc index 190e8691..35f870ec 100644 --- a/snapshot/linux/process_snapshot_linux.cc +++ b/snapshot/linux/process_snapshot_linux.cc @@ -43,13 +43,29 @@ bool ProcessSnapshotLinux::Initialize(PtraceConnection* connection) { InitializeThreads(); InitializeModules(); + InitializeAnnotations(); INITIALIZATION_STATE_SET_VALID(initialized_); return true; } +pid_t ProcessSnapshotLinux::FindThreadWithStackAddress( + VMAddress stack_address) { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + for (const auto& thread : process_reader_.Threads()) { + if (stack_address >= thread.stack_region_address && + stack_address < + thread.stack_region_address + thread.stack_region_size) { + return thread.tid; + } + } + return -1; +} + bool ProcessSnapshotLinux::InitializeException( - LinuxVMAddress exception_info_address) { + LinuxVMAddress exception_info_address, + pid_t exception_thread_id) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); DCHECK(!exception_); @@ -60,6 +76,10 @@ bool ProcessSnapshotLinux::InitializeException( return false; } + if (exception_thread_id >= 0) { + info.thread_id = exception_thread_id; + } + exception_.reset(new internal::ExceptionSnapshotLinux()); if (!exception_->Initialize(&process_reader_, info.siginfo_address, @@ -140,12 +160,12 @@ void ProcessSnapshotLinux::GetCrashpadOptions( *options = local_options; } -pid_t ProcessSnapshotLinux::ProcessID() const { +crashpad::ProcessID ProcessSnapshotLinux::ProcessID() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return process_reader_.ProcessID(); } -pid_t ProcessSnapshotLinux::ParentProcessID() const { +crashpad::ProcessID ProcessSnapshotLinux::ParentProcessID() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return process_reader_.ParentProcessID(); } @@ -234,6 +254,11 @@ std::vector ProcessSnapshotLinux::ExtraMemory() const { return std::vector(); } +const ProcessMemory* ProcessSnapshotLinux::Memory() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return process_reader_.Memory(); +} + void ProcessSnapshotLinux::InitializeThreads() { const std::vector& process_reader_threads = process_reader_.Threads(); @@ -253,11 +278,21 @@ void ProcessSnapshotLinux::InitializeModules() { std::make_unique(reader_module.name, reader_module.elf_reader, reader_module.type, - &memory_range_); + &memory_range_, + process_reader_.Memory()); if (module->Initialize()) { modules_.push_back(std::move(module)); } } } +void ProcessSnapshotLinux::InitializeAnnotations() { +#if defined(OS_ANDROID) + const std::string& abort_message = process_reader_.AbortMessage(); + if (!abort_message.empty()) { + annotations_simple_map_["abort_message"] = abort_message; + } +#endif +} + } // namespace crashpad diff --git a/snapshot/linux/process_snapshot_linux.h b/snapshot/linux/process_snapshot_linux.h index 49d648f9..06b72aff 100644 --- a/snapshot/linux/process_snapshot_linux.h +++ b/snapshot/linux/process_snapshot_linux.h @@ -39,6 +39,7 @@ #include "util/linux/ptrace_connection.h" #include "util/misc/initialization_state_dcheck.h" #include "util/misc/uuid.h" +#include "util/process/process_id.h" #include "util/process/process_memory_range.h" namespace crashpad { @@ -58,11 +59,23 @@ class ProcessSnapshotLinux final : public ProcessSnapshot { //! an appropriate message logged. bool Initialize(PtraceConnection* connection); + //! \brief Finds the thread whose stack contains \a stack_address. + //! + //! \param[in] stack_address A stack address to search for. + //! \return The thread ID of the thread whose stack contains \a stack_address + //! or -1 if no matching thread is found. + pid_t FindThreadWithStackAddress(VMAddress stack_address); + //! \brief Initializes the object's exception. //! //! \param[in] exception_info The address of an ExceptionInformation in the //! target process' address space. - bool InitializeException(LinuxVMAddress exception_info); + //! \param[in] exception_thread_id The thread ID to assocaite the thread with. + //! Optional. If -1, the exception thread will be identified by the + //! ExceptionInformation struct which contains the thread ID in the target + //! process' namespace. + bool InitializeException(LinuxVMAddress exception_info, + pid_t exception_thread_id = -1); //! \brief Sets the value to be returned by ReportID(). //! @@ -78,15 +91,16 @@ class ProcessSnapshotLinux final : public ProcessSnapshot { //! ClientID() will return an identifier consisting entirely of zeroes. void SetClientID(const UUID& client_id) { client_id_ = client_id; } - //! \brief Sets the value to be returned by AnnotationsSimpleMap(). + //! \brief Add an annotation to be returned by AnnotationsSimpleMap(). //! - //! All process annotations are under the control of the snapshot + //! Most process annotations are under the control of the snapshot //! producer, which may call this method to establish these annotations. - //! Contrast this with module annotations, which are under the control of the - //! process being snapshotted. - void SetAnnotationsSimpleMap( - const std::map& annotations_simple_map) { - annotations_simple_map_ = annotations_simple_map; + //! On Android Q or later, the process snapshot may add an "abort_message" + //! annotation, which will contain the abort message passed to the + //! android_set_abort_message() function. Contrast this with module + //! annotations, which are under the control of the process being snapshotted. + void AddAnnotation(const std::string& key, const std::string& value) { + annotations_simple_map_[key] = value; } //! \brief Returns options from CrashpadInfo structures found in modules in @@ -98,8 +112,8 @@ class ProcessSnapshotLinux final : public ProcessSnapshot { // ProcessSnapshot: - pid_t ProcessID() const override; - pid_t ParentProcessID() const override; + crashpad::ProcessID ProcessID() const override; + crashpad::ProcessID ParentProcessID() const override; void SnapshotTime(timeval* snapshot_time) const override; void ProcessStartTime(timeval* start_time) const override; void ProcessCPUTimes(timeval* user_time, timeval* system_time) const override; @@ -115,10 +129,12 @@ class ProcessSnapshotLinux final : public ProcessSnapshot { std::vector MemoryMap() const override; std::vector Handles() const override; std::vector ExtraMemory() const override; + const ProcessMemory* Memory() const override; private: void InitializeThreads(); void InitializeModules(); + void InitializeAnnotations(); std::map annotations_simple_map_; timeval snapshot_time_; diff --git a/snapshot/linux/system_snapshot_linux_test.cc b/snapshot/linux/system_snapshot_linux_test.cc index 46d3845f..f3013b54 100644 --- a/snapshot/linux/system_snapshot_linux_test.cc +++ b/snapshot/linux/system_snapshot_linux_test.cc @@ -77,7 +77,8 @@ TEST(SystemSnapshotLinux, Basic) { EXPECT_PRED1( [](std::string vendor) { - return vendor == "GenuineIntel" || vendor == "AuthenticAMD"; + return vendor == "GenuineIntel" || vendor == "AuthenticAMD" || + vendor == "HygonGenuine"; }, system.CPUVendor()); diff --git a/snapshot/linux/thread_snapshot_linux.cc b/snapshot/linux/thread_snapshot_linux.cc index 8ffbfe8b..e3e2bebd 100644 --- a/snapshot/linux/thread_snapshot_linux.cc +++ b/snapshot/linux/thread_snapshot_linux.cc @@ -23,6 +23,109 @@ namespace crashpad { namespace internal { +namespace { + +int ComputeThreadPriority(int static_priority, + int sched_policy, + int nice_value) { + // Map Linux scheduling policy, static priority, and nice value into a + // single int value. + // + // The possible policies in order of approximate priority (low to high) are + // SCHED_IDLE + // SCHED_BATCH + // SCHED_OTHER + // SCHED_RR + // SCHED_FIFO + // + // static_priority is not used for OTHER, BATCH, or IDLE and should be 0. + // For FIFO and RR, static_priority should range from 1 to 99 with 99 being + // the highest priority. + // + // nice value ranges from -20 to 19, with -20 being highest priority + + enum class Policy : uint8_t { + kUnknown = 0, + kIdle, + kBatch, + kOther, + kRR, + kFIFO + }; + + struct LinuxPriority { +#if defined(ARCH_CPU_LITTLE_ENDIAN) + // nice values affect how dynamic priorities are updated, which only + // matters for threads with the same static priority. + uint8_t nice_value = 0; + + // The scheduling policy also affects how threads with the same static + // priority are ordered, but has greater impact than nice value. + Policy policy = Policy::kUnknown; + + // The static priority is the most significant in determining overall + // priority. + uint8_t static_priority = 0; + + // Put this in the most significant byte position to prevent negative + // priorities. + uint8_t unused = 0; +#elif defined(ARCH_CPU_BIG_ENDIAN) + uint8_t unused = 0; + uint8_t static_priority = 0; + Policy policy = Policy::kUnknown; + uint8_t nice_value = 0; +#endif // ARCH_CPU_LITTLE_ENDIAN + }; + static_assert(sizeof(LinuxPriority) <= sizeof(int), "priority is too large"); + + LinuxPriority prio; + + // Lower nice values have higher priority, so negate them and add 20 to put + // them in the range 1-40 with 40 being highest priority. + if (nice_value < -20 || nice_value > 19) { + LOG(WARNING) << "invalid nice value " << nice_value; + prio.nice_value = 0; + } else { + prio.nice_value = -1 * nice_value + 20; + } + + switch (sched_policy) { + case SCHED_IDLE: + prio.policy = Policy::kIdle; + break; + case SCHED_BATCH: + prio.policy = Policy::kBatch; + break; + case SCHED_OTHER: + prio.policy = Policy::kOther; + break; + case SCHED_RR: + prio.policy = Policy::kRR; + break; + case SCHED_FIFO: + prio.policy = Policy::kFIFO; + break; + default: + prio.policy = Policy::kUnknown; + LOG(WARNING) << "Unknown scheduling policy " << sched_policy; + } + + if (static_priority < 0 || static_priority > 99) { + LOG(WARNING) << "invalid static priority " << static_priority; + } + prio.static_priority = static_priority; + + int priority; + if (!ReinterpretBytes(prio, &priority)) { + LOG(ERROR) << "Couldn't set priority"; + return -1; + } + return priority; +} + +} // namespace + ThreadSnapshotLinux::ThreadSnapshotLinux() : ThreadSnapshot(), context_union_(), @@ -31,11 +134,9 @@ ThreadSnapshotLinux::ThreadSnapshotLinux() thread_specific_data_address_(0), thread_id_(-1), priority_(-1), - initialized_() { -} + initialized_() {} -ThreadSnapshotLinux::~ThreadSnapshotLinux() { -} +ThreadSnapshotLinux::~ThreadSnapshotLinux() {} bool ThreadSnapshotLinux::Initialize(ProcessReaderLinux* process_reader, const ProcessReaderLinux::Thread& thread) { @@ -89,7 +190,7 @@ bool ThreadSnapshotLinux::Initialize(ProcessReaderLinux* process_reader, #error Port. #endif - stack_.Initialize(process_reader, + stack_.Initialize(process_reader->Memory(), thread.stack_region_address, thread.stack_region_size); @@ -98,98 +199,11 @@ bool ThreadSnapshotLinux::Initialize(ProcessReaderLinux* process_reader, thread_id_ = thread.tid; - // Map Linux scheduling policy, static priority, and nice value into a single - // int value. - // - // The possible policies in order of approximate priority (low to high) are - // SCHED_IDLE - // SCHED_BATCH - // SCHED_OTHER - // SCHED_RR - // SCHED_FIFO - // - // static_priority is not used for OTHER, BATCH, or IDLE and should be 0. - // For FIFO and RR, static_priority should range from 1 to 99 with 99 being - // the highest priority. - // - // nice value ranges from -20 to 19, with -20 being highest priority - - enum class Policy : uint8_t { - kUnknown = 0, - kIdle, - kBatch, - kOther, - kRR, - kFIFO - }; - - struct LinuxPriority { -#if defined(ARCH_CPU_LITTLE_ENDIAN) - // nice values affect how dynamic priorities are updated, which only matters - // for threads with the same static priority. - uint8_t nice_value = 0; - - // The scheduling policy also affects how threads with the same static - // priority are ordered, but has greater impact than nice value. - Policy policy = Policy::kUnknown; - - // The static priority is the most significant in determining overall - // priority. - uint8_t static_priority = 0; - - // Put this in the most significant byte position to prevent negative - // priorities. - uint8_t unused = 0; -#elif defined(ARCH_CPU_BIG_ENDIAN) - uint8_t unused = 0; - uint8_t static_priority = 0; - Policy policy = Policy::kUnknown; - uint8_t nice_value = 0; -#endif // ARCH_CPU_LITTLE_ENDIAN - }; - static_assert(sizeof(LinuxPriority) <= sizeof(int), "priority is too large"); - - LinuxPriority prio; - - // Lower nice values have higher priority, so negate them and add 20 to put - // them in the range 1-40 with 40 being highest priority. - if (thread.nice_value < -20 || thread.nice_value > 19) { - LOG(WARNING) << "invalid nice value " << thread.nice_value; - prio.nice_value = 0; - } else { - prio.nice_value = -1 * thread.nice_value + 20; - } - - switch (thread.sched_policy) { - case SCHED_IDLE: - prio.policy = Policy::kIdle; - break; - case SCHED_BATCH: - prio.policy = Policy::kBatch; - break; - case SCHED_OTHER: - prio.policy = Policy::kOther; - break; - case SCHED_RR: - prio.policy = Policy::kRR; - break; - case SCHED_FIFO: - prio.policy = Policy::kFIFO; - break; - default: - prio.policy = Policy::kUnknown; - LOG(WARNING) << "Unknown scheduling policy " << thread.sched_policy; - } - - if (thread.static_priority < 0 || thread.static_priority > 99) { - LOG(WARNING) << "invalid static priority " << thread.static_priority; - } - prio.static_priority = thread.static_priority; - - if (!ReinterpretBytes(prio, &priority_)) { - LOG(ERROR) << "Couldn't set priority"; - return false; - } + priority_ = + thread.have_priorities + ? ComputeThreadPriority( + thread.static_priority, thread.sched_policy, thread.nice_value) + : -1; INITIALIZATION_STATE_SET_VALID(initialized_); return true; diff --git a/snapshot/linux/thread_snapshot_linux.h b/snapshot/linux/thread_snapshot_linux.h index 17e471f3..44cc6f6d 100644 --- a/snapshot/linux/thread_snapshot_linux.h +++ b/snapshot/linux/thread_snapshot_linux.h @@ -73,7 +73,7 @@ class ThreadSnapshotLinux final : public ThreadSnapshot { #endif // ARCH_CPU_X86_FAMILY } context_union_; CPUContext context_; - MemorySnapshotGeneric stack_; + MemorySnapshotGeneric stack_; LinuxVMAddress thread_specific_data_address_; pid_t thread_id_; int priority_; diff --git a/snapshot/mac/cpu_context_mac.cc b/snapshot/mac/cpu_context_mac.cc index c91e3318..03893c40 100644 --- a/snapshot/mac/cpu_context_mac.cc +++ b/snapshot/mac/cpu_context_mac.cc @@ -436,6 +436,136 @@ void InitializeCPUContextX86_64(CPUContextX86_64* context, } // namespace internal +#elif defined(ARCH_CPU_ARM_FAMILY) + +namespace { + +void InitializeCPUContextARM64Thread( + CPUContextARM64* context, + const arm_thread_state64_t* arm_thread_state64) { + // The structures of context->regs and arm_thread_state64->__x are laid out + // identically for this copy, even though the members are organized + // differently. Because of this difference, there can't be a static assert + // similar to the one below for fpsimd. + memcpy(context->regs, arm_thread_state64->__x, sizeof(context->regs)); + context->sp = arm_thread_state64->__sp; + context->pc = arm_thread_state64->__pc; + context->spsr = + static_castspsr)>(arm_thread_state64->__cpsr); +} + +void InitializeCPUContextARM64Neon(CPUContextARM64* context, + const arm_neon_state64_t* arm_neon_state64) { + static_assert(sizeof(context->fpsimd) == sizeof(arm_neon_state64->__v), + "fpsimd context size mismatch"); + memcpy(context->fpsimd, arm_neon_state64->__v, sizeof(arm_neon_state64->__v)); + context->fpsr = arm_neon_state64->__fpsr; + context->fpcr = arm_neon_state64->__fpcr; +} + +thread_state_flavor_t InitializeCPUContextARM64Flavor( + CPUContextARM64* context, + thread_state_flavor_t flavor, + ConstThreadState state, + mach_msg_type_number_t state_count) { + mach_msg_type_number_t expected_state_count; + switch (flavor) { + case ARM_THREAD_STATE: + expected_state_count = ARM_THREAD_STATE_COUNT; + break; + case ARM_THREAD_STATE64: + expected_state_count = ARM_THREAD_STATE64_COUNT; + break; + case ARM_NEON_STATE64: + expected_state_count = ARM_NEON_STATE64_COUNT; + break; + case THREAD_STATE_NONE: { + // This may happen without error when called without exception-style + // flavor data, or even from an exception handler when the exception + // behavior is EXCEPTION_DEFAULT. + return flavor; + } + default: + LOG(WARNING) << "unhandled flavor " << flavor; + return THREAD_STATE_NONE; + } + + if (state_count < expected_state_count) { + LOG(WARNING) << "expected state_count " << expected_state_count + << " for flavor " << flavor << ", observed " << state_count; + return THREAD_STATE_NONE; + } + + switch (flavor) { + case ARM_THREAD_STATE: { + const arm_unified_thread_state_t* arm_thread_state = + reinterpret_cast(state); + if (arm_thread_state->ash.flavor != ARM_THREAD_STATE64) { + LOG(WARNING) << "expected flavor ARM_THREAD_STATE64, observed " + << arm_thread_state->ash.flavor; + return THREAD_STATE_NONE; + } + return InitializeCPUContextARM64Flavor( + context, + arm_thread_state->ash.flavor, + reinterpret_cast(&arm_thread_state->ts_64), + arm_thread_state->ash.count); + } + + case ARM_THREAD_STATE64: { + const arm_thread_state64_t* arm_thread_state = + reinterpret_cast(state); + InitializeCPUContextARM64Thread(context, arm_thread_state); + return ARM_THREAD_STATE64; + } + + case ARM_NEON_STATE64: { + const arm_neon_state64_t* arm_neon_state = + reinterpret_cast(state); + InitializeCPUContextARM64Neon(context, arm_neon_state); + return ARM_NEON_STATE64; + } + + case THREAD_STATE_NONE: { + // This may happen without error when called without exception-style + // flavor data, or even from an exception handler when the exception + // behavior is EXCEPTION_DEFAULT. + return flavor; + } + + default: { + NOTREACHED(); + return THREAD_STATE_NONE; + } + } +} + +} // namespace + +namespace internal { + +void InitializeCPUContextARM64(CPUContextARM64* context, + thread_state_flavor_t flavor, + ConstThreadState state, + mach_msg_type_number_t state_count, + const arm_thread_state64_t* arm_thread_state64, + const arm_neon_state64_t* arm_neon_state64) { + thread_state_flavor_t set_flavor = THREAD_STATE_NONE; + if (flavor != THREAD_STATE_NONE) { + set_flavor = + InitializeCPUContextARM64Flavor(context, flavor, state, state_count); + } + + if (set_flavor != ARM_THREAD_STATE64) { + InitializeCPUContextARM64Thread(context, arm_thread_state64); + } + if (set_flavor != ARM_NEON_STATE64) { + InitializeCPUContextARM64Neon(context, arm_neon_state64); + } +} + +} // namespace internal + #endif } // namespace crashpad diff --git a/snapshot/mac/cpu_context_mac.h b/snapshot/mac/cpu_context_mac.h index 30281c16..05c035a1 100644 --- a/snapshot/mac/cpu_context_mac.h +++ b/snapshot/mac/cpu_context_mac.h @@ -108,6 +108,44 @@ void InitializeCPUContextX86_64(CPUContextX86_64* context, const x86_float_state64_t* x86_float_state64, const x86_debug_state64_t* x86_debug_state64); +#elif defined(ARCH_CPU_ARM_FAMILY) || DOXYGEN +//! \brief Initializes a CPUContextARM64 structure from native context +//! structures on iOS. +//! +//! \a flavor, \a state, and \a state_count may be supplied by exception +//! handlers in order for the \a context parameter to be initialized by the +//! thread state received by the exception handler to the extent possible. In +//! that case, whatever thread state specified by these three parameters will +//! supersede \a arm_thread_state64 or \a arm_neon_state64. If thread state in +//! this format is not available, \a flavor may be set to `THREAD_STATE_NONE`, +//! and all of \a arm_thread_state64 abd \a arm_neon_state64 will be honored. +//! +//! If \a flavor, \a state, and \a state_count are provided but do not contain +//! valid values, a message will be logged and their values will be ignored as +//! though \a flavor were specified as `THREAD_STATE_NONE`. +//! +//! \param[out] context The CPUContextARM64 structure to initialize. +//! \param[in] flavor The native thread state flavor of \a state. This may be +//! `ARM_THREAD_STATE64`, `ARM_THREAD_STATE` or `ARM_NEON_STATE64`. It may +//! also be `THREAD_STATE_NONE` if \a state is not supplied (and is +//! `nullptr`). +//! \param[in] state The native thread state, which may be a casted pointer to +//! `arm_thread_state64_t`, `arm_unified_thread_state` or +//! `arm_neon_state64_t`. This parameter may be `nullptr` to not supply this +//! data, in which case \a flavor must be `THREAD_STATE_NONE`. If a +//! “universal” structure is used, it must carry 64-bit state data of the +//! correct type. +//! \param[in] state_count The number of `int`-sized units in \a state. This +//! may be 0 if \a state is `nullptr`. +//! \param[in] arm_thread_state64 The state of the thread’s integer registers. +//! \param[in] arm_neon_state64 The state of the thread’s floating-point +//! registers. +void InitializeCPUContextARM64(CPUContextARM64* context, + thread_state_flavor_t flavor, + ConstThreadState state, + mach_msg_type_number_t state_count, + const arm_thread_state64_t* arm_thread_state64, + const arm_neon_state64_t* arm_neon_state64); #endif } // namespace internal diff --git a/snapshot/mac/exception_snapshot_mac.cc b/snapshot/mac/exception_snapshot_mac.cc index 50d1a121..8eefac9f 100644 --- a/snapshot/mac/exception_snapshot_mac.cc +++ b/snapshot/mac/exception_snapshot_mac.cc @@ -142,8 +142,13 @@ bool ExceptionSnapshotMac::Initialize(ProcessReaderMac* process_reader, thread_id_ = thread->id; - // Normally, the exception address is present in code[1] for EXC_BAD_ACCESS - // exceptions, but not for other types of exceptions. + // Normally, for EXC_BAD_ACCESS exceptions, the exception address is present + // in code[1]. It may or may not be the instruction pointer address (usually + // it’s not). code[1] may carry the exception address for other exception + // types too, but it’s not guaranteed. But for all other exception types, the + // instruction pointer will be the exception address, and in fact will be + // equal to codes[1] when it’s carrying the exception address. In those cases, + // just use the instruction pointer directly. bool code_1_is_exception_address = exception_ == EXC_BAD_ACCESS; #if defined(ARCH_CPU_X86_FAMILY) diff --git a/snapshot/mac/mach_o_image_annotations_reader.cc b/snapshot/mac/mach_o_image_annotations_reader.cc index b02acaea..eefd7f44 100644 --- a/snapshot/mac/mach_o_image_annotations_reader.cc +++ b/snapshot/mac/mach_o_image_annotations_reader.cc @@ -26,7 +26,6 @@ #include "snapshot/mac/mach_o_image_reader.h" #include "snapshot/mac/process_reader_mac.h" #include "snapshot/snapshot_constants.h" -#include "util/mach/task_memory.h" #include "util/stdlib/strnlen.h" namespace crashpad { @@ -179,9 +178,9 @@ void MachOImageAnnotationsReader::ReadCrashpadSimpleAnnotations( } } -// TODO(rsesek): When there is a platform-agnostic remote memory reader -// interface available, use it so that the implementation is not duplicated -// in the PEImageAnnotationsReader. +// TODO(https://crbug.com/crashpad/270): Replace implementations of +// ReadCrashpadAnnotationsList and ReadCrashpadSimpleAnnotations with the +// platform-agnostic implementations in ImageAnnotationReader. void MachOImageAnnotationsReader::ReadCrashpadAnnotationsList( std::vector* annotations) const { process_types::CrashpadInfo crashpad_info; diff --git a/snapshot/mac/mach_o_image_annotations_reader_test.cc b/snapshot/mac/mach_o_image_annotations_reader_test.cc index 69302503..8659a141 100644 --- a/snapshot/mac/mach_o_image_annotations_reader_test.cc +++ b/snapshot/mac/mach_o_image_annotations_reader_test.cc @@ -461,7 +461,13 @@ TEST(MachOImageAnnotationsReader, CrashAbort) { test_mach_o_image_annotations_reader.Run(); } -TEST(MachOImageAnnotationsReader, CrashModuleInitialization) { +#if defined(ADDRESS_SANITIZER) +// https://crbug.com/844396 +#define MAYBE_CrashModuleInitialization DISABLED_CrashModuleInitialization +#else +#define MAYBE_CrashModuleInitialization CrashModuleInitialization +#endif +TEST(MachOImageAnnotationsReader, MAYBE_CrashModuleInitialization) { TestMachOImageAnnotationsReader test_mach_o_image_annotations_reader( TestMachOImageAnnotationsReader::kCrashModuleInitialization); test_mach_o_image_annotations_reader.Run(); diff --git a/snapshot/mac/mach_o_image_reader.cc b/snapshot/mac/mach_o_image_reader.cc index 6baee770..bd258caf 100644 --- a/snapshot/mac/mach_o_image_reader.cc +++ b/snapshot/mac/mach_o_image_reader.cc @@ -22,6 +22,7 @@ #include #include "base/logging.h" +#include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "client/crashpad_info.h" #include "snapshot/mac/mach_o_image_segment_reader.h" @@ -182,7 +183,7 @@ bool MachOImageReader::Initialize(ProcessReaderMac* process_reader, // This vector is parallel to the kLoadCommandReaders array, and tracks // whether a singleton load command matching the |command| field has been // found yet. - std::vector singleton_indices(arraysize(kLoadCommandReaders), + std::vector singleton_indices(base::size(kLoadCommandReaders), kInvalidSegmentIndex); size_t offset = mach_header.Size(); @@ -236,7 +237,7 @@ bool MachOImageReader::Initialize(ProcessReaderMac* process_reader, } for (size_t reader_index = 0; - reader_index < arraysize(kLoadCommandReaders); + reader_index < base::size(kLoadCommandReaders); ++reader_index) { if (load_command.cmd != kLoadCommandReaders[reader_index].command) { continue; diff --git a/snapshot/mac/mach_o_image_segment_reader_test.cc b/snapshot/mac/mach_o_image_segment_reader_test.cc index 2da97e85..4731a5fc 100644 --- a/snapshot/mac/mach_o_image_segment_reader_test.cc +++ b/snapshot/mac/mach_o_image_segment_reader_test.cc @@ -16,7 +16,7 @@ #include -#include "base/macros.h" +#include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "gtest/gtest.h" @@ -63,7 +63,7 @@ TEST(MachOImageSegmentReader, SegmentNameString) { SEG_IMPORT, }; - for (size_t index = 0; index < arraysize(kSegmentTestData); ++index) { + for (size_t index = 0; index < base::size(kSegmentTestData); ++index) { EXPECT_EQ( MachOImageSegmentReader::SegmentNameString(kSegmentTestData[index]), kSegmentTestData[index]) @@ -106,7 +106,7 @@ TEST(MachOImageSegmentReader, SectionNameString) { SECT_ICON_TIFF, }; - for (size_t index = 0; index < arraysize(kSectionTestData); ++index) { + for (size_t index = 0; index < base::size(kSectionTestData); ++index) { EXPECT_EQ( MachOImageSegmentReader::SectionNameString(kSectionTestData[index]), kSectionTestData[index]) @@ -169,7 +169,7 @@ TEST(MachOImageSegmentReader, SegmentAndSectionNameString) { {SEG_IMPORT, "", "__IMPORT,"}, }; - for (size_t index = 0; index < arraysize(kSegmentAndSectionTestData); + for (size_t index = 0; index < base::size(kSegmentAndSectionTestData); ++index) { const auto& test = kSegmentAndSectionTestData[index]; EXPECT_EQ(MachOImageSegmentReader::SegmentAndSectionNameString( diff --git a/snapshot/mac/mach_o_image_symbol_table_reader.cc b/snapshot/mac/mach_o_image_symbol_table_reader.cc index 361253ce..cbdac402 100644 --- a/snapshot/mac/mach_o_image_symbol_table_reader.cc +++ b/snapshot/mac/mach_o_image_symbol_table_reader.cc @@ -23,7 +23,7 @@ #include "base/strings/stringprintf.h" #include "util/mac/checked_mach_address_range.h" -#include "util/mach/task_memory.h" +#include "util/process/process_memory_mac.h" namespace crashpad { @@ -108,7 +108,7 @@ class MachOImageSymbolTableReaderInitializer { return false; } - std::unique_ptr string_table; + std::unique_ptr string_table; for (size_t symbol_index = 0; symbol_index < symbol_count; ++symbol_index) { const process_types::nlist& symbol = symbols[symbol_index]; std::string symbol_info = base::StringPrintf(", symbol index %zu%s", diff --git a/snapshot/mac/mach_o_image_symbol_table_reader.h b/snapshot/mac/mach_o_image_symbol_table_reader.h index 841b479a..b1bcc3a2 100644 --- a/snapshot/mac/mach_o_image_symbol_table_reader.h +++ b/snapshot/mac/mach_o_image_symbol_table_reader.h @@ -58,7 +58,7 @@ class MachOImageSymbolTableReader { // there aren’t expected to be very many of those that performance would // become a problem. In reality, std::unordered_map does not appear to provide // a performance advantage. It appears that the memory copies currently done - // by TaskMemory::Read() have substantially more impact on symbol table + // by ProcessMemoryMac::Read() have substantially more impact on symbol table // operations. // // This is public so that the type is available to diff --git a/snapshot/mac/module_snapshot_mac.cc b/snapshot/mac/module_snapshot_mac.cc index 41608978..947744db 100644 --- a/snapshot/mac/module_snapshot_mac.cc +++ b/snapshot/mac/module_snapshot_mac.cc @@ -14,8 +14,8 @@ #include "snapshot/mac/module_snapshot_mac.h" -#include #include +#include #include "base/files/file_path.h" #include "base/strings/stringprintf.h" @@ -34,11 +34,9 @@ ModuleSnapshotMac::ModuleSnapshotMac() timestamp_(0), mach_o_image_reader_(nullptr), process_reader_(nullptr), - initialized_() { -} + initialized_() {} -ModuleSnapshotMac::~ModuleSnapshotMac() { -} +ModuleSnapshotMac::~ModuleSnapshotMac() {} bool ModuleSnapshotMac::Initialize( ProcessReaderMac* process_reader, @@ -170,6 +168,11 @@ std::string ModuleSnapshotMac::DebugFileName() const { return base::FilePath(Name()).BaseName().value(); } +std::vector ModuleSnapshotMac::BuildID() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return std::vector(); +} + std::vector ModuleSnapshotMac::AnnotationsVector() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); MachOImageAnnotationsReader annotations_reader( diff --git a/snapshot/mac/module_snapshot_mac.h b/snapshot/mac/module_snapshot_mac.h index fe2d40a1..d78c3266 100644 --- a/snapshot/mac/module_snapshot_mac.h +++ b/snapshot/mac/module_snapshot_mac.h @@ -77,6 +77,7 @@ class ModuleSnapshotMac final : public ModuleSnapshot { ModuleType GetModuleType() const override; void UUIDAndAge(crashpad::UUID* uuid, uint32_t* age) const override; std::string DebugFileName() const override; + std::vector BuildID() const override; std::vector AnnotationsVector() const override; std::map AnnotationsSimpleMap() const override; std::vector AnnotationObjects() const override; diff --git a/snapshot/mac/process_reader_mac.cc b/snapshot/mac/process_reader_mac.cc index e142fd2e..61dc3a1f 100644 --- a/snapshot/mac/process_reader_mac.cc +++ b/snapshot/mac/process_reader_mac.cc @@ -92,7 +92,7 @@ ProcessReaderMac::ProcessReaderMac() threads_(), modules_(), module_readers_(), - task_memory_(), + process_memory_(), task_(TASK_NULL), initialized_(), is_64_bit_(false), @@ -113,9 +113,11 @@ bool ProcessReaderMac::Initialize(task_t task) { return false; } - is_64_bit_ = process_info_.Is64Bit(); + if (!process_memory_.Initialize(task)) { + return false; + } - task_memory_.reset(new TaskMemory(task)); + is_64_bit_ = process_info_.Is64Bit(); task_ = task; INITIALIZATION_STATE_SET_VALID(initialized_); @@ -441,7 +443,7 @@ void ProcessReaderMac::InitializeModules() { Module module; module.timestamp = image_info.imageFileModDate; - if (!task_memory_->ReadCString(image_info.imageFilePath, &module.name)) { + if (!process_memory_.ReadCString(image_info.imageFilePath, &module.name)) { LOG(WARNING) << "could not read dyld_image_info::imageFilePath"; // Proceed anyway with an empty module name. } diff --git a/snapshot/mac/process_reader_mac.h b/snapshot/mac/process_reader_mac.h index 91836dbb..9feb8b07 100644 --- a/snapshot/mac/process_reader_mac.h +++ b/snapshot/mac/process_reader_mac.h @@ -27,9 +27,9 @@ #include "base/macros.h" #include "build/build_config.h" -#include "util/mach/task_memory.h" #include "util/misc/initialization_state_dcheck.h" #include "util/posix/process_info.h" +#include "util/process/process_memory_mac.h" namespace crashpad { @@ -138,7 +138,7 @@ class ProcessReaderMac { bool CPUTimes(timeval* user_time, timeval* system_time) const; //! \return Accesses the memory of the target task. - TaskMemory* Memory() { return task_memory_.get(); } + const ProcessMemoryMac* Memory() const { return &process_memory_; } //! \return The threads that are in the task (process). The first element (at //! index `0`) corresponds to the main thread. @@ -232,7 +232,7 @@ class ProcessReaderMac { std::vector threads_; // owns send rights std::vector modules_; std::vector> module_readers_; - std::unique_ptr task_memory_; + ProcessMemoryMac process_memory_; task_t task_; // weak InitializationStateDcheck initialized_; diff --git a/snapshot/mac/process_reader_mac_test.cc b/snapshot/mac/process_reader_mac_test.cc index 59884a6d..9325b2c4 100644 --- a/snapshot/mac/process_reader_mac_test.cc +++ b/snapshot/mac/process_reader_mac_test.cc @@ -15,6 +15,7 @@ #include "snapshot/mac/process_reader_mac.h" #include +#include #include #include #include @@ -26,8 +27,9 @@ #include #include "base/logging.h" -#include "base/mac/scoped_mach_port.h" +#include "base/mac/mach_logging.h" #include "base/posix/eintr_wrapper.h" +#include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "build/build_config.h" #include "gtest/gtest.h" @@ -63,7 +65,7 @@ TEST(ProcessReaderMac, SelfBasic) { EXPECT_EQ(process_reader.ParentProcessID(), getppid()); static constexpr char kTestMemory[] = "Some test memory"; - char buffer[arraysize(kTestMemory)]; + char buffer[base::size(kTestMemory)]; ASSERT_TRUE(process_reader.Memory()->Read( FromPointerCast(kTestMemory), sizeof(kTestMemory), @@ -127,8 +129,8 @@ TEST(ProcessReaderMac, ChildBasic) { // This function CHECKs success and returns the thread ID directly. uint64_t PthreadToThreadID(pthread_t pthread) { uint64_t thread_id; - int rv = pthread_threadid_np(pthread, &thread_id); - CHECK_EQ(rv, 0); + errno = pthread_threadid_np(pthread, &thread_id); + PCHECK(errno == 0) << "pthread_threadid_np"; return thread_id; } @@ -410,6 +412,18 @@ TEST(ProcessReaderMac, SelfSeveralThreads) { EXPECT_TRUE(found_thread_self); } +uint64_t GetThreadID() { + thread_identifier_info info; + mach_msg_type_number_t info_count = THREAD_IDENTIFIER_INFO_COUNT; + kern_return_t kr = thread_info(MachThreadSelf(), + THREAD_IDENTIFIER_INFO, + reinterpret_cast(&info), + &info_count); + MACH_CHECK(kr == KERN_SUCCESS, kr) << "thread_info"; + + return info.thread_id; +} + class ProcessReaderThreadedChild final : public MachMultiprocess { public: explicit ProcessReaderThreadedChild(size_t thread_count) @@ -463,7 +477,7 @@ class ProcessReaderThreadedChild final : public MachMultiprocess { // This thread isn’t part of the thread pool, but the parent will be able // to inspect it. Write an entry for it. - uint64_t thread_id = PthreadToThreadID(pthread_self()); + uint64_t thread_id = GetThreadID(); CheckedWriteFile(write_handle, &thread_id, sizeof(thread_id)); @@ -599,11 +613,11 @@ class ScopedOpenCLNoOpKernel { const size_t source_lengths[] = { strlen(sources[0]), }; - static_assert(arraysize(sources) == arraysize(source_lengths), + static_assert(base::size(sources) == base::size(source_lengths), "arrays must be parallel"); program_ = clCreateProgramWithSource( - context_, arraysize(sources), sources, source_lengths, &rv); + context_, base::size(sources), sources, source_lengths, &rv); ASSERT_EQ(rv, CL_SUCCESS) << "clCreateProgramWithSource"; rv = clBuildProgram( diff --git a/snapshot/mac/process_snapshot_mac.cc b/snapshot/mac/process_snapshot_mac.cc index cf5233a9..528e9213 100644 --- a/snapshot/mac/process_snapshot_mac.cc +++ b/snapshot/mac/process_snapshot_mac.cc @@ -217,6 +217,11 @@ std::vector ProcessSnapshotMac::ExtraMemory() const { return std::vector(); } +const ProcessMemory* ProcessSnapshotMac::Memory() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return process_reader_.Memory(); +} + void ProcessSnapshotMac::InitializeThreads() { const std::vector& process_reader_threads = process_reader_.Threads(); diff --git a/snapshot/mac/process_snapshot_mac.h b/snapshot/mac/process_snapshot_mac.h index 06bac749..2edc2220 100644 --- a/snapshot/mac/process_snapshot_mac.h +++ b/snapshot/mac/process_snapshot_mac.h @@ -131,6 +131,7 @@ class ProcessSnapshotMac final : public ProcessSnapshot { std::vector MemoryMap() const override; std::vector Handles() const override; std::vector ExtraMemory() const override; + const ProcessMemory* Memory() const override; private: // Initializes threads_ on behalf of Initialize(). diff --git a/snapshot/mac/process_types.cc b/snapshot/mac/process_types.cc index 65c39ea5..0a1b7f95 100644 --- a/snapshot/mac/process_types.cc +++ b/snapshot/mac/process_types.cc @@ -20,9 +20,9 @@ #include -#include "base/macros.h" +#include "base/stl_util.h" #include "snapshot/mac/process_types/internal.h" -#include "util/mach/task_memory.h" +#include "util/process/process_memory_mac.h" namespace crashpad { namespace { @@ -74,7 +74,7 @@ using UInt64Array4 = uint64_t[4]; template <> inline void Assign(UInt64Array4* destination, const UInt32Array4& source) { - for (size_t index = 0; index < arraysize(source); ++index) { + for (size_t index = 0; index < base::size(source); ++index) { (*destination)[index] = source[index]; } } diff --git a/snapshot/mac/process_types/custom.cc b/snapshot/mac/process_types/custom.cc index 7c7b172a..a76c3ebb 100644 --- a/snapshot/mac/process_types/custom.cc +++ b/snapshot/mac/process_types/custom.cc @@ -19,13 +19,16 @@ #include #include +#include #include #include "base/logging.h" #include "base/numerics/safe_math.h" +#include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "snapshot/mac/process_types/internal.h" -#include "util/mach/task_memory.h" +#include "util/mac/mac_util.h" +#include "util/process/process_memory_mac.h" #if !DOXYGEN @@ -36,13 +39,13 @@ namespace internal { namespace { template -bool ReadIntoAndZero(TaskMemory* task_memory, +bool ReadIntoAndZero(const ProcessMemoryMac* process_memory, mach_vm_address_t address, mach_vm_size_t size, T* specific) { DCHECK_LE(size, sizeof(*specific)); - if (!task_memory->Read(address, size, specific)) { + if (!process_memory->Read(address, size, specific)) { return false; } @@ -84,14 +87,14 @@ bool ReadIntoVersioned(ProcessReaderMac* process_reader, return false; } - TaskMemory* task_memory = process_reader->Memory(); + const ProcessMemoryMac* process_memory = process_reader->Memory(); decltype(specific->version) version; - if (!task_memory->Read(field_address, sizeof(version), &version)) { + if (!process_memory->Read(field_address, sizeof(version), &version)) { return false; } const size_t size = T::ExpectedSizeForVersion(version); - return ReadIntoAndZero(task_memory, address, size, specific); + return ReadIntoAndZero(process_memory, address, size, specific); } template @@ -103,9 +106,9 @@ bool ReadIntoSized(ProcessReaderMac* process_reader, return false; } - TaskMemory* task_memory = process_reader->Memory(); + const ProcessMemoryMac* process_memory = process_reader->Memory(); decltype(specific->size) size; - if (!task_memory->Read(address + offsetof(T, size), sizeof(size), &size)) { + if (!process_memory->Read(address + offsetof(T, size), sizeof(size), &size)) { return false; } @@ -115,7 +118,7 @@ bool ReadIntoSized(ProcessReaderMac* process_reader, } size = std::min(static_cast(size), sizeof(*specific)); - return ReadIntoAndZero(task_memory, address, size, specific); + return ReadIntoAndZero(process_memory, address, size, specific); } } // namespace @@ -139,18 +142,38 @@ size_t dyld_all_image_infos::ExpectedSizeForVersion( offsetof(dyld_all_image_infos, sharedCacheSlide), // 11 offsetof(dyld_all_image_infos, sharedCacheUUID), // 12 offsetof(dyld_all_image_infos, infoArrayChangeTimestamp), // 13 - offsetof(dyld_all_image_infos, end_14_15), // 14 - offsetof(dyld_all_image_infos, end_14_15), // 15 + offsetof(dyld_all_image_infos, end_14), // 14 + std::numeric_limits::max(), // 15, see below sizeof(dyld_all_image_infos), // 16 }; - if (version >= arraysize(kSizeForVersion)) { - return kSizeForVersion[arraysize(kSizeForVersion) - 1]; + if (version >= base::size(kSizeForVersion)) { + return kSizeForVersion[base::size(kSizeForVersion) - 1]; } static_assert(std::is_unsigned::value, "version must be unsigned"); - return kSizeForVersion[version]; + + if (version == 15) { + // Disambiguate between the two different layouts for version 15. The + // original one introduced in macOS 10.12 had the same size as version 14. + // The revised one in macOS 10.13 grew. It’s safe to assume that the + // dyld_all_image_infos structure came from the same system that’s now + // interpreting it, so use an OS version check. + int mac_os_x_minor_version = MacOSXMinorVersion(); + if (mac_os_x_minor_version == 12) { + return offsetof(dyld_all_image_infos, end_14); + } + + DCHECK_GE(mac_os_x_minor_version, 13); + DCHECK_LE(mac_os_x_minor_version, 14); + return offsetof(dyld_all_image_infos, platform); + } + + size_t size = kSizeForVersion[version]; + DCHECK_NE(size, std::numeric_limits::max()); + + return size; } // static diff --git a/snapshot/mac/process_types/dyld_images.proctype b/snapshot/mac/process_types/dyld_images.proctype index f92a8009..3b040854 100644 --- a/snapshot/mac/process_types/dyld_images.proctype +++ b/snapshot/mac/process_types/dyld_images.proctype @@ -121,23 +121,29 @@ PROCESS_TYPE_STRUCT_BEGIN(dyld_all_image_infos) // the runtimes that use versions 14 and 15 were built with SDKs that did not // have this extra padding, it’s necessary to treat the element at index 4 on // 32-bit systems as outside of the version 14 and 15 structure. This is why - // |reserved| is only declared a 4-element array, with a special end_14_15 - // member (not present in the native definition) available to indicate the - // end of the native version 14 and 15 structure, preceding the padding in the - // 32-bit structure that would natively be addressed at index 4 of |reserved|. - // Treat reserved_4_32 as only available in version 16 of the structure. + // |reserved| is only declared a 4-element array, with a special end_14 member + // (not present in the native definition) available to indicate the end of the + // native version 14 structure and the 10.12 version 15 structure, preceding + // the padding in the 32-bit structure that would natively be addressed at + // index 4 of |reserved|. Treat reserved_4_32 as only available in version 16 + // of the structure. PROCESS_TYPE_STRUCT_MEMBER(UIntPtr, reserved, [4]) PROCESS_TYPE_STRUCT_MEMBER(Reserved64_64Only, reserved_4_64) PROCESS_TYPE_STRUCT_MEMBER(Reserved64_64Only, reserved_5) PROCESS_TYPE_STRUCT_MEMBER(Reserved64_64Only, reserved_6) PROCESS_TYPE_STRUCT_MEMBER(Reserved64_64Only, reserved_7) PROCESS_TYPE_STRUCT_MEMBER(Reserved64_64Only, reserved_8) - PROCESS_TYPE_STRUCT_MEMBER(Nothing, end_14_15) + PROCESS_TYPE_STRUCT_MEMBER(Nothing, end_14) PROCESS_TYPE_STRUCT_MEMBER(Reserved32_32Only, reserved_4_32) - // Version 16 (macOS 10.13) + // Version 15 (macOS 10.13). incorrectly claims that + // these were introduced at version 16. These fields are not present in macOS + // 10.12, which also identifies its structure as version 15. PROCESS_TYPE_STRUCT_MEMBER(UIntPtr, compact_dyld_image_info_addr) PROCESS_TYPE_STRUCT_MEMBER(ULong, compact_dyld_image_info_size) // size_t + + // Version 16 (macOS 10.15) + PROCESS_TYPE_STRUCT_MEMBER(uint32_t, platform) // dyld_platform_t PROCESS_TYPE_STRUCT_END(dyld_all_image_infos) #endif // ! PROCESS_TYPE_STRUCT_IMPLEMENT_INTERNAL_READ_INTO && diff --git a/snapshot/mac/process_types_test.cc b/snapshot/mac/process_types_test.cc index f116c4dd..0ad28697 100644 --- a/snapshot/mac/process_types_test.cc +++ b/snapshot/mac/process_types_test.cc @@ -18,9 +18,10 @@ #include #include +#include #include -#include "base/macros.h" +#include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "build/build_config.h" #include "gtest/gtest.h" @@ -47,13 +48,11 @@ namespace { TEST(ProcessTypes, DyldImagesSelf) { // Get the in-process view of dyld_all_image_infos, and check it for sanity. const dyld_all_image_infos* self_image_infos = DyldGetAllImageInfos(); - int mac_os_x_minor_version = MacOSXMinorVersion(); + const int mac_os_x_minor_version = MacOSXMinorVersion(); - // The 10.13 SDK defines dyld_all_image_infos version 16 and says that it’s - // used on 10.13, but 10.13db1 17A264c uses version 15. - // - // TODO(mark): Recheck later in the beta period, up to the 10.13 release. - if (mac_os_x_minor_version >= 12) { + if (mac_os_x_minor_version >= 15) { + EXPECT_GE(self_image_infos->version, 16u); + } else if (mac_os_x_minor_version >= 12) { EXPECT_GE(self_image_infos->version, 15u); } else if (mac_os_x_minor_version >= 9) { EXPECT_GE(self_image_infos->version, 13u); @@ -104,7 +103,7 @@ TEST(ProcessTypes, DyldImagesSelf) { ProcessReaderMac process_reader; ASSERT_TRUE(process_reader.Initialize(mach_task_self())); -#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_13 +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_15 constexpr uint32_t kDyldAllImageInfosVersionInSDK = 16; #elif MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12 constexpr uint32_t kDyldAllImageInfosVersionInSDK = 15; @@ -120,12 +119,33 @@ TEST(ProcessTypes, DyldImagesSelf) { // Make sure that the size of the structure as declared in the SDK matches the // size expected for the version of the structure that the SDK describes. - EXPECT_EQ(process_types::dyld_all_image_infos::ExpectedSizeForVersion( - &process_reader, kDyldAllImageInfosVersionInSDK), - sizeof(dyld_all_image_infos)); + // + // There are two possible layouts for version 15, and the + // ExpectedSizeForVersion() implementation infers the correct one based on the + // run-time OS version, so if the SDK defines the version 15 structure, this + // test can only be performed if the run-time OS natively uses the same format + // structure as the SDK. + bool test_expected_size_for_version_matches_sdk_sizeof; +#if MAC_OS_X_VERSION_MAX_ALLOWED == MAC_OS_X_VERSION_10_12 + test_expected_size_for_version_matches_sdk_sizeof = + mac_os_x_minor_version == 12; +#elif MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_13 && \ + MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_15 + test_expected_size_for_version_matches_sdk_sizeof = + mac_os_x_minor_version >= 13 && mac_os_x_minor_version <= 14; +#else + test_expected_size_for_version_matches_sdk_sizeof = true; +#endif + + if (test_expected_size_for_version_matches_sdk_sizeof) { + EXPECT_EQ(process_types::dyld_all_image_infos::ExpectedSizeForVersion( + &process_reader, kDyldAllImageInfosVersionInSDK), + sizeof(dyld_all_image_infos)); + } // Make sure that the computed sizes of various versions of this structure are // correct at different bitnessses. + constexpr size_t kSpecialCase = std::numeric_limits::max(); constexpr struct { uint32_t version; size_t size_32; @@ -144,13 +164,40 @@ TEST(ProcessTypes, DyldImagesSelf) { {12, 84, 160}, {13, 104, 184}, {14, 164, 304}, - {15, 164, 304}, - {16, 176, 320}, + {15, kSpecialCase, kSpecialCase}, + {16, 184, 328}, }; - for (size_t index = 0; index < arraysize(kVersionsAndSizes); ++index) { + for (size_t index = 0; index < base::size(kVersionsAndSizes); ++index) { uint32_t version = kVersionsAndSizes[index].version; SCOPED_TRACE(base::StringPrintf("index %zu, version %u", index, version)); + if (version == 15) { + if (mac_os_x_minor_version == 12) { + EXPECT_EQ(process_types::internal::dyld_all_image_infos< + process_types::internal::Traits32>:: + ExpectedSizeForVersion(version), + 164u); + EXPECT_EQ(process_types::internal::dyld_all_image_infos< + process_types::internal::Traits64>:: + ExpectedSizeForVersion(version), + 304u); + } else if (mac_os_x_minor_version >= 13 && mac_os_x_minor_version <= 14) { + EXPECT_EQ(process_types::internal::dyld_all_image_infos< + process_types::internal::Traits32>:: + ExpectedSizeForVersion(version), + 176u); + EXPECT_EQ(process_types::internal::dyld_all_image_infos< + process_types::internal::Traits64>:: + ExpectedSizeForVersion(version), + 320u); + } + + continue; + } + + ASSERT_NE(kVersionsAndSizes[index].size_32, kSpecialCase); + ASSERT_NE(kVersionsAndSizes[index].size_64, kSpecialCase); + EXPECT_EQ( process_types::internal::dyld_all_image_infos< process_types::internal::Traits32>::ExpectedSizeForVersion(version), @@ -268,8 +315,7 @@ TEST(ProcessTypes, DyldImagesSelf) { self_image_infos->sharedCacheBaseAddress); EXPECT_EQ(proctype_image_infos.dyldPath, reinterpret_cast(self_image_infos->dyldPath)); - for (size_t index = 0; - index < arraysize(self_image_infos->notifyPorts); + for (size_t index = 0; index < base::size(self_image_infos->notifyPorts); ++index) { EXPECT_EQ(proctype_image_infos.notifyPorts[index], self_image_infos->notifyPorts[index]) @@ -289,8 +335,7 @@ TEST(ProcessTypes, DyldImagesSelf) { // process_types version. It’s difficult to compare the reserved fields in // these older SDKs, so only do it where the declarations match. if (proctype_image_infos.version >= 14) { - for (size_t index = 0; - index < arraysize(proctype_image_infos.reserved); + for (size_t index = 0; index < base::size(proctype_image_infos.reserved); ++index) { EXPECT_EQ(proctype_image_infos.reserved[index], implicit_cast(self_image_infos->reserved[index])) @@ -308,7 +353,7 @@ TEST(ProcessTypes, DyldImagesSelf) { #endif #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_13 - if (proctype_image_infos.version >= 16) { + if (proctype_image_infos.version >= 15 && mac_os_x_minor_version >= 13) { EXPECT_EQ(proctype_image_infos.compact_dyld_image_info_addr, self_image_infos->compact_dyld_image_info_addr); EXPECT_EQ(proctype_image_infos.compact_dyld_image_info_size, @@ -316,6 +361,12 @@ TEST(ProcessTypes, DyldImagesSelf) { } #endif +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_15 + if (proctype_image_infos.version >= 16) { + EXPECT_EQ(proctype_image_infos.platform, self_image_infos->platform); + } +#endif + if (proctype_image_infos.version >= 1) { std::vector proctype_image_info_vector( proctype_image_infos.infoArrayCount); diff --git a/snapshot/mac/system_snapshot_mac.cc b/snapshot/mac/system_snapshot_mac.cc index 21f254ee..5410be08 100644 --- a/snapshot/mac/system_snapshot_mac.cc +++ b/snapshot/mac/system_snapshot_mac.cc @@ -14,6 +14,7 @@ #include "snapshot/mac/system_snapshot_mac.h" +#include #include #include #include @@ -22,6 +23,7 @@ #include #include "base/logging.h" +#include "base/scoped_clear_last_error.h" #include "base/strings/stringprintf.h" #include "build/build_config.h" #include "snapshot/cpu_context.h" @@ -34,11 +36,16 @@ namespace crashpad { namespace { +template +int ReadIntSysctlByName_NoLog(const char* name, T* value) { + size_t value_len = sizeof(*value); + return sysctlbyname(name, value, &value_len, nullptr, 0); +} + template T ReadIntSysctlByName(const char* name, T default_value) { T value; - size_t value_len = sizeof(value); - if (sysctlbyname(name, &value, &value_len, nullptr, 0) != 0) { + if (ReadIntSysctlByName_NoLog(name, &value) != 0) { PLOG(WARNING) << "sysctlbyname " << name; return default_value; } @@ -338,7 +345,35 @@ std::string SystemSnapshotMac::MachineDescription() const { bool SystemSnapshotMac::NXEnabled() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - return ReadIntSysctlByName("kern.nx", 0); + + int value; + if (ReadIntSysctlByName_NoLog("kern.nx", &value) != 0) { + { + // Support for the kern.nx sysctlbyname is compiled out of production + // kernels on macOS 10.14.5 and later, although it’s available in + // development and debug kernels. Compare 10.14.3 + // xnu-4903.241.1/bsd/kern/kern_sysctl.c to 10.15.0 + // xnu-6153.11.26/bsd/kern/kern_sysctl.c (10.14.4 and 10.14.5 xnu source + // are not yet available). In newer production kernels, NX is always + // enabled. See 10.15.0 xnu-6153.11.26/osfmk/x86_64/pmap.c nx_enabled. +#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_14 + const bool nx_always_enabled = true; +#else // DT >= 10.14 + base::ScopedClearLastError reset_errno; + const bool nx_always_enabled = MacOSXMinorVersion() >= 14; +#endif // DT >= 10.14 + if (nx_always_enabled) { + return true; + } + } + + // Even if sysctlbyname should have worked, NX is enabled by default in all + // supported configurations, so return true even while warning. + PLOG(WARNING) << "sysctlbyname kern.nx"; + return true; + } + + return value; } void SystemSnapshotMac::TimeZone(DaylightSavingTimeStatus* dst_status, diff --git a/snapshot/mac/system_snapshot_mac_test.cc b/snapshot/mac/system_snapshot_mac_test.cc index 69048eb0..2b665924 100644 --- a/snapshot/mac/system_snapshot_mac_test.cc +++ b/snapshot/mac/system_snapshot_mac_test.cc @@ -126,6 +126,12 @@ TEST_F(SystemSnapshotMacTest, MachineDescription) { EXPECT_FALSE(system_snapshot().MachineDescription().empty()); } +TEST_F(SystemSnapshotMacTest, NXEnabled) { + // Assume NX will always be enabled, as it was always enabled by default on + // all supported versions of macOS. + EXPECT_TRUE(system_snapshot().NXEnabled()); +} + } // namespace } // namespace test } // namespace crashpad diff --git a/snapshot/mac/thread_snapshot_mac.cc b/snapshot/mac/thread_snapshot_mac.cc index f45fc418..8fbb3e70 100644 --- a/snapshot/mac/thread_snapshot_mac.cc +++ b/snapshot/mac/thread_snapshot_mac.cc @@ -49,7 +49,7 @@ bool ThreadSnapshotMac::Initialize( thread_specific_data_address_ = process_reader_thread.thread_specific_data_address; - stack_.Initialize(process_reader, + stack_.Initialize(process_reader->Memory(), process_reader_thread.stack_region_address, process_reader_thread.stack_region_size); diff --git a/snapshot/mac/thread_snapshot_mac.h b/snapshot/mac/thread_snapshot_mac.h index 8f5d722b..946b0085 100644 --- a/snapshot/mac/thread_snapshot_mac.h +++ b/snapshot/mac/thread_snapshot_mac.h @@ -70,7 +70,7 @@ class ThreadSnapshotMac final : public ThreadSnapshot { } context_union_; #endif CPUContext context_; - MemorySnapshotGeneric stack_; + MemorySnapshotGeneric stack_; uint64_t thread_id_; uint64_t thread_specific_data_address_; thread_t thread_; diff --git a/snapshot/memory_snapshot.h b/snapshot/memory_snapshot.h index 9e274a0e..5dbac5ad 100644 --- a/snapshot/memory_snapshot.h +++ b/snapshot/memory_snapshot.h @@ -111,31 +111,6 @@ 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 index 402e913e..d8bc9ec7 100644 --- a/snapshot/memory_snapshot_generic.h +++ b/snapshot/memory_snapshot_generic.h @@ -19,6 +19,7 @@ #include #include "base/macros.h" +#include "base/numerics/safe_math.h" #include "snapshot/memory_snapshot.h" #include "util/misc/address_types.h" #include "util/misc/initialization_state_dcheck.h" @@ -28,9 +29,8 @@ namespace 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 +//! system. Works on multiple platforms by using a platform-specific +//! ProcessMemory object. class MemorySnapshotGeneric final : public MemorySnapshot { public: MemorySnapshotGeneric() = default; @@ -42,25 +42,25 @@ class MemorySnapshotGeneric final : public MemorySnapshot { //! until Read() is called, and the memory snapshot data is discared when //! Read() returns. //! - //! \param[in] process_reader A reader for the process being snapshotted. + //! \param[in] process_memory A reader for the process being snapshotted. //! \param[in] address The base address of the memory region to snapshot, in //! the snapshot process’ address space. //! \param[in] size The size of the memory region to snapshot. - void Initialize(ProcessReaderType* process_reader, + void Initialize(const ProcessMemory* process_memory, VMAddress address, VMSize size) { INITIALIZATION_STATE_SET_INITIALIZING(initialized_); - process_reader_ = process_reader; + process_memory_ = process_memory; address_ = address; - size_ = size; + size_ = base::checked_cast(size); INITIALIZATION_STATE_SET_VALID(initialized_); } // MemorySnapshot: uint64_t Address() const override { - INITIALIZATION_STATE_DCHECK_VALID(initialized_); - return address_; + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return address_; } size_t Size() const override { @@ -76,7 +76,7 @@ class MemorySnapshotGeneric final : public MemorySnapshot { } std::unique_ptr buffer(new uint8_t[size_]); - if (!process_reader_->Memory()->Read(address_, size_, buffer.get())) { + if (!process_memory_->Read(address_, size_, buffer.get())) { return false; } return delegate->MemorySnapshotDelegateRead(buffer.get(), size_); @@ -84,7 +84,19 @@ class MemorySnapshotGeneric final : public MemorySnapshot { const MemorySnapshot* MergeWithOtherSnapshot( const MemorySnapshot* other) const override { - return MergeWithOtherSnapshotImpl(this, other); + const MemorySnapshotGeneric* other_as_memory_snapshot_concrete = + reinterpret_cast(other); + if (process_memory_ != other_as_memory_snapshot_concrete->process_memory_) { + LOG(ERROR) << "different process_memory_ for snapshots"; + return nullptr; + } + CheckedRange merged(0, 0); + if (!LoggingDetermineMergedRange(this, other, &merged)) + return nullptr; + + auto result = std::make_unique(); + result->Initialize(process_memory_, merged.base(), merged.size()); + return result.release(); } private: @@ -93,9 +105,9 @@ class MemorySnapshotGeneric final : public MemorySnapshot { const T* self, const MemorySnapshot* other); - ProcessReaderType* process_reader_; // weak - uint64_t address_; - uint64_t size_; + const ProcessMemory* process_memory_; // weak + VMAddress address_; + size_t size_; InitializationStateDcheck initialized_; DISALLOW_COPY_AND_ASSIGN(MemorySnapshotGeneric); diff --git a/snapshot/minidump/exception_snapshot_minidump.cc b/snapshot/minidump/exception_snapshot_minidump.cc new file mode 100644 index 00000000..afd32828 --- /dev/null +++ b/snapshot/minidump/exception_snapshot_minidump.cc @@ -0,0 +1,111 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "snapshot/minidump/exception_snapshot_minidump.h" + +#include "snapshot/minidump/minidump_string_reader.h" + +namespace crashpad { +namespace internal { + +ExceptionSnapshotMinidump::ExceptionSnapshotMinidump() + : ExceptionSnapshot(), + minidump_exception_stream_(), + context_(), + exception_information_(), + initialized_() {} + +ExceptionSnapshotMinidump::~ExceptionSnapshotMinidump() {} + +bool ExceptionSnapshotMinidump::Initialize(FileReaderInterface* file_reader, + CPUArchitecture arch, + RVA minidump_exception_stream_rva) { + DCHECK(initialized_.is_uninitialized()); + initialized_.set_invalid(); + + std::vector minidump_context; + + if (!file_reader->SeekSet(minidump_exception_stream_rva)) { + return false; + } + + if (!file_reader->ReadExactly(&minidump_exception_stream_, + sizeof(minidump_exception_stream_))) { + return false; + } + + const size_t num_parameters = + minidump_exception_stream_.ExceptionRecord.NumberParameters; + for (size_t i = 0; i < num_parameters; ++i) { + exception_information_.push_back( + minidump_exception_stream_.ExceptionRecord.ExceptionInformation[i]); + } + + if (!file_reader->SeekSet(minidump_exception_stream_.ThreadContext.Rva)) { + return false; + } + + minidump_context.resize(minidump_exception_stream_.ThreadContext.DataSize); + + if (!file_reader->ReadExactly(minidump_context.data(), + minidump_context.size())) { + return false; + } + + if (!context_.Initialize(arch, minidump_context)) { + return false; + } + + initialized_.set_valid(); + return true; +} + +const CPUContext* ExceptionSnapshotMinidump::Context() const { + DCHECK(initialized_.is_valid()); + return context_.Get(); +} + +uint64_t ExceptionSnapshotMinidump::ThreadID() const { + DCHECK(initialized_.is_valid()); + return minidump_exception_stream_.ThreadId; +} + +uint32_t ExceptionSnapshotMinidump::Exception() const { + DCHECK(initialized_.is_valid()); + return minidump_exception_stream_.ExceptionRecord.ExceptionCode; +} + +uint32_t ExceptionSnapshotMinidump::ExceptionInfo() const { + DCHECK(initialized_.is_valid()); + return minidump_exception_stream_.ExceptionRecord.ExceptionFlags; +} + +uint64_t ExceptionSnapshotMinidump::ExceptionAddress() const { + DCHECK(initialized_.is_valid()); + return minidump_exception_stream_.ExceptionRecord.ExceptionAddress; +} + +const std::vector& ExceptionSnapshotMinidump::Codes() const { + DCHECK(initialized_.is_valid()); + return exception_information_; +} + +std::vector ExceptionSnapshotMinidump::ExtraMemory() + const { + DCHECK(initialized_.is_valid()); + return std::vector(); +} + +} // namespace internal +} // namespace crashpad diff --git a/snapshot/minidump/exception_snapshot_minidump.h b/snapshot/minidump/exception_snapshot_minidump.h new file mode 100644 index 00000000..62f834fe --- /dev/null +++ b/snapshot/minidump/exception_snapshot_minidump.h @@ -0,0 +1,76 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_SNAPSHOT_MINIDUMP_EXCEPTION_SNAPSHOT_MINIDUMP_H_ +#define CRASHPAD_SNAPSHOT_MINIDUMP_EXCEPTION_SNAPSHOT_MINIDUMP_H_ + +#include +#include + +#include "build/build_config.h" +#include "snapshot/cpu_context.h" +#include "snapshot/exception_snapshot.h" +#include "snapshot/minidump/minidump_context_converter.h" +#include "util/file/file_reader.h" +#include "util/misc/initialization_state.h" + +namespace crashpad { +namespace internal { + +//! \brief An ExceptionSnapshot based on a minidump file. +class ExceptionSnapshotMinidump final : public ExceptionSnapshot { + public: + ExceptionSnapshotMinidump(); + ~ExceptionSnapshotMinidump() override; + + //! \brief Initializes the object. + //! + //! \param[in] file_reader A file reader corresponding to a minidump file. + //! The file reader must support seeking. + //! \param[in] arch The CPU architecture of this snapshot. + //! \param[in] minidump_exception_stream_rva The file offset in \a file_reader + //! at which the MINIDUMP_EXCEPTION_STREAM structure is located. + //! + //! \return `true` if the snapshot could be created, `false` otherwise with + //! an appropriate message logged. + bool Initialize(FileReaderInterface* file_reader, + CPUArchitecture arch, + RVA minidump_exception_stream_rva); + + // ExceptionSnapshot: + const CPUContext* Context() const override; + uint64_t ThreadID() const override; + uint32_t Exception() const override; + uint32_t ExceptionInfo() const override; + uint64_t ExceptionAddress() const override; + const std::vector& Codes() const override; + std::vector ExtraMemory() const override; + + // Allow callers to explicitly check whether this exception snapshot has been + // initialized. + bool IsValid() const { return initialized_.is_valid(); } + + private: + MINIDUMP_EXCEPTION_STREAM minidump_exception_stream_; + MinidumpContextConverter context_; + std::vector exception_information_; + InitializationState initialized_; + + DISALLOW_COPY_AND_ASSIGN(ExceptionSnapshotMinidump); +}; + +} // namespace internal +} // namespace crashpad + +#endif // CRASHPAD_SNAPSHOT_MINIDUMP_EXCEPTION_SNAPSHOT_MINIDUMP_H_ diff --git a/snapshot/minidump/memory_snapshot_minidump.cc b/snapshot/minidump/memory_snapshot_minidump.cc new file mode 100644 index 00000000..2c6c899a --- /dev/null +++ b/snapshot/minidump/memory_snapshot_minidump.cc @@ -0,0 +1,111 @@ +// Copyright 2018 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "snapshot/minidump/memory_snapshot_minidump.h" + +#include + +#include "base/numerics/safe_math.h" + +namespace crashpad { +namespace internal { + +MemorySnapshotMinidump::MemorySnapshotMinidump() + : MemorySnapshot(), + address_(0), + data_(), + initialized_() {} + +MemorySnapshotMinidump::~MemorySnapshotMinidump() {} + +bool MemorySnapshotMinidump::Initialize(FileReaderInterface* file_reader, + RVA location) { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + + MINIDUMP_MEMORY_DESCRIPTOR descriptor; + + if (!file_reader->SeekSet(location)) { + return false; + } + + if (!file_reader->ReadExactly(&descriptor, sizeof(descriptor))) { + return false; + } + + address_ = descriptor.StartOfMemoryRange; + data_.resize(descriptor.Memory.DataSize); + + if (!file_reader->SeekSet(descriptor.Memory.Rva)) { + return false; + } + + if (!file_reader->ReadExactly(data_.data(), data_.size())) { + return false; + } + + INITIALIZATION_STATE_SET_VALID(initialized_); + return true; +} + +uint64_t MemorySnapshotMinidump::Address() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return address_; +} + +size_t MemorySnapshotMinidump::Size() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return data_.size(); +} + +bool MemorySnapshotMinidump::Read(Delegate* delegate) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return delegate->MemorySnapshotDelegateRead( + const_cast(data_.data()), data_.size()); +} + +const MemorySnapshot* MemorySnapshotMinidump::MergeWithOtherSnapshot( + const MemorySnapshot* other) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + // TODO: Verify type of other + auto other_cast = reinterpret_cast(other); + + INITIALIZATION_STATE_DCHECK_VALID(other_cast->initialized_); + + if (other_cast->address_ < address_) { + return other_cast->MergeWithOtherSnapshot(this); + } + + CheckedRange merged(0, 0); + if (!LoggingDetermineMergedRange(this, other, &merged)) { + return nullptr; + } + + auto result = std::make_unique(); + result->address_ = merged.base(); + result->data_ = data_; + + if (result->data_.size() == merged.size()) { + return result.release(); + } + + result->data_.resize( + base::checked_cast(other_cast->address_ - address_)); + result->data_.insert(result->data_.end(), other_cast->data_.begin(), + other_cast->data_.end()); + return result.release(); +} + +} // namespace internal +} // namespace crashpad diff --git a/snapshot/minidump/memory_snapshot_minidump.h b/snapshot/minidump/memory_snapshot_minidump.h new file mode 100644 index 00000000..69521be0 --- /dev/null +++ b/snapshot/minidump/memory_snapshot_minidump.h @@ -0,0 +1,63 @@ +// Copyright 2018 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_SNAPSHOT_MINIDUMP_MEMORY_SNAPSHOT_MINIDUMP_H_ +#define CRASHPAD_SNAPSHOT_MINIDUMP_MEMORY_SNAPSHOT_MINIDUMP_H_ + +#include +#include + +#include + +#include "base/macros.h" +#include "snapshot/memory_snapshot.h" +#include "util/file/file_reader.h" +#include "util/misc/initialization_state_dcheck.h" + +namespace crashpad { +namespace internal { +class MemorySnapshotMinidump : public MemorySnapshot { + public: + MemorySnapshotMinidump(); + ~MemorySnapshotMinidump() override; + + //! \brief Initializes the object. + //! + //! \param[in] file_reader A file reader corresponding to a minidump file. + //! The file reader must support seeking. + //! \param[in] location The location within the file where we will find a + //! MINIDUMP_MEMORY_DESCRIPTOR from which to initialize this object. + //! + //! \return `true` if the snapshot could be created, `false` otherwise with + //! an appropriate message logged. + bool Initialize(FileReaderInterface* file_reader, RVA location); + + uint64_t Address() const override; + size_t Size() const override; + bool Read(Delegate* delegate) const override; + const MemorySnapshot* MergeWithOtherSnapshot( + const MemorySnapshot* other) const override; + + private: + uint64_t address_; + std::vector data_; + InitializationStateDcheck initialized_; + + DISALLOW_COPY_AND_ASSIGN(MemorySnapshotMinidump); +}; + +} // namespace internal +} // namespace crashpad + +#endif // CRASHPAD_SNAPSHOT_MINIDUMP_MEMORY_SNAPSHOT_MINIDUMP_H_ diff --git a/snapshot/minidump/minidump_context_converter.cc b/snapshot/minidump/minidump_context_converter.cc new file mode 100644 index 00000000..0c840dea --- /dev/null +++ b/snapshot/minidump/minidump_context_converter.cc @@ -0,0 +1,275 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "snapshot/minidump/minidump_context_converter.h" + +#include "base/stl_util.h" +#include "minidump/minidump_context.h" + +namespace crashpad { +namespace internal { + +MinidumpContextConverter::MinidumpContextConverter() : initialized_() { + context_.architecture = CPUArchitecture::kCPUArchitectureUnknown; +} + +bool MinidumpContextConverter::Initialize( + CPUArchitecture arch, + const std::vector& minidump_context) { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + + if (minidump_context.size() == 0) { + // Thread has no context. + context_.architecture = CPUArchitecture::kCPUArchitectureUnknown; + INITIALIZATION_STATE_SET_VALID(initialized_); + return true; + } + + context_.architecture = arch; + + if (context_.architecture == CPUArchitecture::kCPUArchitectureX86) { + context_memory_.resize(sizeof(CPUContextX86)); + context_.x86 = reinterpret_cast(context_memory_.data()); + const MinidumpContextX86* src = + reinterpret_cast(minidump_context.data()); + if (minidump_context.size() < sizeof(MinidumpContextX86)) { + return false; + } + + if (!(src->context_flags & kMinidumpContextX86)) { + return false; + } + + if (src->context_flags & kMinidumpContextX86Extended) { + context_.x86->fxsave = src->fxsave; + } else if (src->context_flags & kMinidumpContextX86FloatingPoint) { + CPUContextX86::FsaveToFxsave(src->fsave, &context_.x86->fxsave); + } + + context_.x86->eax = src->eax; + context_.x86->ebx = src->ebx; + context_.x86->ecx = src->ecx; + context_.x86->edx = src->edx; + context_.x86->edi = src->edi; + context_.x86->esi = src->esi; + context_.x86->ebp = src->ebp; + context_.x86->esp = src->esp; + context_.x86->eip = src->eip; + context_.x86->eflags = src->eflags; + context_.x86->cs = static_cast(src->cs); + context_.x86->ds = static_cast(src->ds); + context_.x86->es = static_cast(src->es); + context_.x86->fs = static_cast(src->fs); + context_.x86->gs = static_cast(src->gs); + context_.x86->ss = static_cast(src->ss); + context_.x86->dr0 = src->dr0; + context_.x86->dr1 = src->dr1; + context_.x86->dr2 = src->dr2; + context_.x86->dr3 = src->dr3; + context_.x86->dr6 = src->dr6; + context_.x86->dr7 = src->dr7; + + // Minidump passes no value for dr4/5. Our output context has space for + // them. According to spec they're obsolete, but when present read as + // aliases for dr6/7, so we'll do this. + context_.x86->dr4 = src->dr6; + context_.x86->dr5 = src->dr7; + } else if (context_.architecture == CPUArchitecture::kCPUArchitectureX86_64) { + context_memory_.resize(sizeof(CPUContextX86_64)); + context_.x86_64 = + reinterpret_cast(context_memory_.data()); + const MinidumpContextAMD64* src = + reinterpret_cast(minidump_context.data()); + if (minidump_context.size() < sizeof(MinidumpContextAMD64)) { + return false; + } + + if (!(src->context_flags & kMinidumpContextAMD64)) { + return false; + } + + context_.x86_64->fxsave = src->fxsave; + context_.x86_64->cs = src->cs; + context_.x86_64->fs = src->fs; + context_.x86_64->gs = src->gs; + context_.x86_64->rflags = src->eflags; + context_.x86_64->dr0 = src->dr0; + context_.x86_64->dr1 = src->dr1; + context_.x86_64->dr2 = src->dr2; + context_.x86_64->dr3 = src->dr3; + context_.x86_64->dr6 = src->dr6; + context_.x86_64->dr7 = src->dr7; + context_.x86_64->rax = src->rax; + context_.x86_64->rcx = src->rcx; + context_.x86_64->rdx = src->rdx; + context_.x86_64->rbx = src->rbx; + context_.x86_64->rsp = src->rsp; + context_.x86_64->rbp = src->rbp; + context_.x86_64->rsi = src->rsi; + context_.x86_64->rdi = src->rdi; + context_.x86_64->r8 = src->r8; + context_.x86_64->r9 = src->r9; + context_.x86_64->r10 = src->r10; + context_.x86_64->r11 = src->r11; + context_.x86_64->r12 = src->r12; + context_.x86_64->r13 = src->r13; + context_.x86_64->r14 = src->r14; + context_.x86_64->r15 = src->r15; + context_.x86_64->rip = src->rip; + + // See comments on x86 above. + context_.x86_64->dr4 = src->dr6; + context_.x86_64->dr5 = src->dr7; + } else if (context_.architecture == CPUArchitecture::kCPUArchitectureARM) { + context_memory_.resize(sizeof(CPUContextARM)); + context_.arm = reinterpret_cast(context_memory_.data()); + const MinidumpContextARM* src = + reinterpret_cast(minidump_context.data()); + if (minidump_context.size() < sizeof(MinidumpContextARM)) { + return false; + } + + if (!(src->context_flags & kMinidumpContextARM)) { + return false; + } + + for (size_t i = 0; i < base::size(src->regs); i++) { + context_.arm->regs[i] = src->regs[i]; + } + + context_.arm->fp = src->fp; + context_.arm->ip = src->ip; + context_.arm->sp = src->sp; + context_.arm->lr = src->lr; + context_.arm->pc = src->pc; + context_.arm->cpsr = src->cpsr; + context_.arm->vfp_regs.fpscr = src->fpscr; + + for (size_t i = 0; i < base::size(src->vfp); i++) { + context_.arm->vfp_regs.vfp[i] = src->vfp[i]; + } + + context_.arm->have_fpa_regs = false; + context_.arm->have_vfp_regs = + !!(src->context_flags & kMinidumpContextARMVFP); + } else if (context_.architecture == CPUArchitecture::kCPUArchitectureARM64) { + context_memory_.resize(sizeof(CPUContextARM64)); + context_.arm64 = reinterpret_cast(context_memory_.data()); + const MinidumpContextARM64* src = + reinterpret_cast(minidump_context.data()); + if (minidump_context.size() < sizeof(MinidumpContextARM64)) { + return false; + } + + if (!(src->context_flags & kMinidumpContextARM64)) { + return false; + } + + for (size_t i = 0; i < base::size(src->regs); i++) { + context_.arm64->regs[i] = src->regs[i]; + } + + context_.arm64->regs[29] = src->fp; + context_.arm64->regs[30] = src->lr; + + for (size_t i = 0; i < base::size(src->fpsimd); i++) { + context_.arm64->fpsimd[i] = src->fpsimd[i]; + } + + context_.arm64->sp = src->sp; + context_.arm64->pc = src->pc; + context_.arm64->fpcr = src->fpcr; + context_.arm64->fpsr = src->fpsr; + context_.arm64->spsr = src->cpsr; + } else if (context_.architecture == CPUArchitecture::kCPUArchitectureMIPSEL) { + context_memory_.resize(sizeof(CPUContextMIPS)); + context_.mipsel = reinterpret_cast(context_memory_.data()); + const MinidumpContextMIPS* src = + reinterpret_cast(minidump_context.data()); + if (minidump_context.size() < sizeof(MinidumpContextMIPS)) { + return false; + } + + if (!(src->context_flags & kMinidumpContextMIPS)) { + return false; + } + + for (size_t i = 0; i < base::size(src->regs); i++) { + context_.mipsel->regs[i] = src->regs[i]; + } + + context_.mipsel->mdhi = static_cast(src->mdhi); + context_.mipsel->mdlo = static_cast(src->mdlo); + context_.mipsel->dsp_control = src->dsp_control; + + for (size_t i = 0; i < base::size(src->hi); i++) { + context_.mipsel->hi[i] = src->hi[i]; + context_.mipsel->lo[i] = src->lo[i]; + } + + context_.mipsel->cp0_epc = static_cast(src->epc); + context_.mipsel->cp0_badvaddr = static_cast(src->badvaddr); + context_.mipsel->cp0_status = src->status; + context_.mipsel->cp0_cause = src->cause; + context_.mipsel->fpcsr = src->fpcsr; + context_.mipsel->fir = src->fir; + + memcpy(&context_.mipsel->fpregs, &src->fpregs, sizeof(src->fpregs)); + } else if (context_.architecture == + CPUArchitecture::kCPUArchitectureMIPS64EL) { + context_memory_.resize(sizeof(CPUContextMIPS64)); + context_.mips64 = + reinterpret_cast(context_memory_.data()); + const MinidumpContextMIPS64* src = + reinterpret_cast(minidump_context.data()); + if (minidump_context.size() < sizeof(MinidumpContextMIPS64)) { + return false; + } + + if (!(src->context_flags & kMinidumpContextMIPS64)) { + return false; + } + + for (size_t i = 0; i < base::size(src->regs); i++) { + context_.mips64->regs[i] = src->regs[i]; + } + + context_.mips64->mdhi = src->mdhi; + context_.mips64->mdlo = src->mdlo; + context_.mips64->dsp_control = src->dsp_control; + + for (size_t i = 0; i < base::size(src->hi); i++) { + context_.mips64->hi[i] = src->hi[i]; + context_.mips64->lo[i] = src->lo[i]; + } + + context_.mips64->cp0_epc = src->epc; + context_.mips64->cp0_badvaddr = src->badvaddr; + context_.mips64->cp0_status = src->status; + context_.mips64->cp0_cause = src->cause; + context_.mips64->fpcsr = src->fpcsr; + context_.mips64->fir = src->fir; + + memcpy(&context_.mips64->fpregs, &src->fpregs, sizeof(src->fpregs)); + } else { + // Architecture is listed as "unknown". + DLOG(ERROR) << "Unknown architecture"; + } + + INITIALIZATION_STATE_SET_VALID(initialized_); + return true; +} + +} // namespace internal +} // namespace crashpad diff --git a/snapshot/minidump/minidump_context_converter.h b/snapshot/minidump/minidump_context_converter.h new file mode 100644 index 00000000..48e36c1f --- /dev/null +++ b/snapshot/minidump/minidump_context_converter.h @@ -0,0 +1,47 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_SNAPSHOT_MINIDUMP_MINIDUMP_CONTEXT_CONVERTER_H_ +#define CRASHPAD_SNAPSHOT_MINIDUMP_MINIDUMP_CONTEXT_CONVERTER_H_ + +#include + +#include "snapshot/cpu_context.h" +#include "util/misc/initialization_state.h" +#include "util/misc/initialization_state_dcheck.h" + +namespace crashpad { +namespace internal { + +class MinidumpContextConverter { + public: + MinidumpContextConverter(); + + bool Initialize(CPUArchitecture arch, + const std::vector& minidump_context); + const CPUContext* Get() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return &context_; + } + + private: + CPUContext context_; + std::vector context_memory_; + InitializationStateDcheck initialized_; +}; + +} // namespace internal +} // namespace crashpad + +#endif // CRASHPAD_SNAPSHOT_MINIDUMP_MINIDUMP_CONTEXT_CONVERTER_H_ diff --git a/snapshot/minidump/minidump_stream.h b/snapshot/minidump/minidump_stream.h new file mode 100644 index 00000000..2b0ac2e1 --- /dev/null +++ b/snapshot/minidump/minidump_stream.h @@ -0,0 +1,44 @@ +// Copyright 2018 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_SNAPSHOT_MINIDUMP_MINIDUMP_STREAM_H_ +#define CRASHPAD_SNAPSHOT_MINIDUMP_MINIDUMP_STREAM_H_ + +#include + +#include + +#include "base/macros.h" + +namespace crashpad { + +//! \brief Stores a minidump stream along with its stream ID. +class MinidumpStream { + public: + MinidumpStream(uint32_t stream_type, std::vector data) + : stream_type_(stream_type), data_(data) {} + + uint32_t stream_type() const { return stream_type_; } + const std::vector& data() const { return data_; } + + private: + uint32_t stream_type_; + std::vector data_; + + DISALLOW_COPY_AND_ASSIGN(MinidumpStream); +}; + +} // namespace crashpad + +#endif // CRASHPAD_SNAPSHOT_MINIDUMP_MINIDUMP_STREAM_H_ diff --git a/snapshot/minidump/minidump_string_reader.cc b/snapshot/minidump/minidump_string_reader.cc index 4a5cb136..d642e9d4 100644 --- a/snapshot/minidump/minidump_string_reader.cc +++ b/snapshot/minidump/minidump_string_reader.cc @@ -17,14 +17,18 @@ #include #include "base/logging.h" +#include "base/strings/utf_string_conversions.h" #include "minidump/minidump_extensions.h" namespace crashpad { namespace internal { -bool ReadMinidumpUTF8String(FileReaderInterface* file_reader, +namespace { + +template +bool ReadMinidumpString(FileReaderInterface* file_reader, RVA rva, - std::string* string) { + StringType* string) { if (rva == 0) { string->clear(); return true; @@ -39,7 +43,7 @@ bool ReadMinidumpUTF8String(FileReaderInterface* file_reader, return false; } - std::string local_string(string_size, '\0'); + StringType local_string(string_size / sizeof((*string)[0]), '\0'); if (!file_reader->ReadExactly(&local_string[0], string_size)) { return false; } @@ -48,5 +52,33 @@ bool ReadMinidumpUTF8String(FileReaderInterface* file_reader, return true; } +} // namespace + +bool ReadMinidumpUTF8String(FileReaderInterface* file_reader, + RVA rva, + std::string* string) { + return ReadMinidumpString(file_reader, rva, string); +} + +bool ReadMinidumpUTF16String(FileReaderInterface* file_reader, + RVA rva, + base::string16* string) { + return ReadMinidumpString(file_reader, rva, string); +} + +bool ReadMinidumpUTF16String(FileReaderInterface* file_reader, + RVA rva, + std::string* string) { + base::string16 string_raw; + + if (!ReadMinidumpString(file_reader, rva, &string_raw)) { + return false; + } + + base::UTF16ToUTF8(string_raw.data(), string_raw.size(), string); + + return true; +} + } // namespace internal } // namespace crashpad diff --git a/snapshot/minidump/minidump_string_reader.h b/snapshot/minidump/minidump_string_reader.h index e5667ec2..b7ecdac3 100644 --- a/snapshot/minidump/minidump_string_reader.h +++ b/snapshot/minidump/minidump_string_reader.h @@ -20,6 +20,7 @@ #include +#include "base/strings/string16.h" #include "util/file/file_reader.h" namespace crashpad { @@ -34,6 +35,24 @@ bool ReadMinidumpUTF8String(FileReaderInterface* file_reader, RVA rva, std::string* string); +//! \brief Reads a MinidumpUTF16String from a minidump file at offset \a rva in +//! \a file_reader, and returns it in \a string. +//! +//! \return `true` on success, with \a string set. `false` on failure, with a +//! message logged. +bool ReadMinidumpUTF16String(FileReaderInterface* file_reader, + RVA rva, + base::string16* string); + +//! \brief Reads a MinidumpUTF16String from a minidump file at offset \a rva in +//! \a file_reader, and returns it in \a string. +//! +//! \return `true` on success, with \a string set. `false` on failure, with a +//! message logged. +bool ReadMinidumpUTF16String(FileReaderInterface* file_reader, + RVA rva, + std::string* string); + } // namespace internal } // namespace crashpad diff --git a/snapshot/minidump/module_snapshot_minidump.cc b/snapshot/minidump/module_snapshot_minidump.cc index 06cd1bb6..a316e83b 100644 --- a/snapshot/minidump/module_snapshot_minidump.cc +++ b/snapshot/minidump/module_snapshot_minidump.cc @@ -14,10 +14,16 @@ #include "snapshot/minidump/module_snapshot_minidump.h" +#include +#include + +#include "base/logging.h" #include "minidump/minidump_extensions.h" #include "snapshot/minidump/minidump_annotation_reader.h" #include "snapshot/minidump/minidump_simple_string_dictionary_reader.h" #include "snapshot/minidump/minidump_string_list_reader.h" +#include "snapshot/minidump/minidump_string_reader.h" +#include "util/misc/pdb_structures.h" namespace crashpad { namespace internal { @@ -27,11 +33,15 @@ ModuleSnapshotMinidump::ModuleSnapshotMinidump() minidump_module_(), annotations_vector_(), annotations_simple_map_(), - initialized_() { -} + annotation_objects_(), + uuid_(), + build_id_(), + name_(), + debug_file_name_(), + age_(0), + initialized_() {} -ModuleSnapshotMinidump::~ModuleSnapshotMinidump() { -} +ModuleSnapshotMinidump::~ModuleSnapshotMinidump() {} bool ModuleSnapshotMinidump::Initialize( FileReaderInterface* file_reader, @@ -53,32 +63,87 @@ bool ModuleSnapshotMinidump::Initialize( return false; } + ReadMinidumpUTF16String(file_reader, minidump_module_.ModuleNameRva, &name_); + + if (minidump_module_.CvRecord.Rva != 0 && + !InitializeModuleCodeView(file_reader)) { + return false; + } + INITIALIZATION_STATE_SET_VALID(initialized_); return true; } +bool ModuleSnapshotMinidump::InitializeModuleCodeView( + FileReaderInterface* file_reader) { + uint32_t signature; + + DCHECK_NE(minidump_module_.CvRecord.Rva, 0u); + + if (minidump_module_.CvRecord.DataSize < sizeof(signature)) { + LOG(ERROR) << "CodeView record in module too small to contain signature"; + return false; + } + + if (!file_reader->SeekSet(minidump_module_.CvRecord.Rva)) { + return false; + } + + std::vector cv_record; + cv_record.resize(minidump_module_.CvRecord.DataSize); + + if (!file_reader->ReadExactly(cv_record.data(), cv_record.size())) { + return false; + } + + signature = *reinterpret_cast(cv_record.data()); + + if (signature == CodeViewRecordPDB70::kSignature) { + if (cv_record.size() < offsetof(CodeViewRecordPDB70, pdb_name)) { + LOG(ERROR) << "CodeView record in module marked as PDB70 but too small"; + return false; + } + + auto cv_record_pdb70 = + reinterpret_cast(cv_record.data()); + + age_ = cv_record_pdb70->age; + uuid_ = cv_record_pdb70->uuid; + + std::copy(cv_record.begin() + offsetof(CodeViewRecordPDB70, pdb_name), + cv_record.end(), + std::back_inserter(debug_file_name_)); + return true; + } + + if (signature == CodeViewRecordBuildID::kSignature) { + std::copy(cv_record.begin() + offsetof(CodeViewRecordBuildID, build_id), + cv_record.end(), + std::back_inserter(build_id_)); + return true; + } + + LOG(ERROR) << "Bad CodeView signature in module"; + return false; +} + std::string ModuleSnapshotMinidump::Name() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - NOTREACHED(); // https://crashpad.chromium.org/bug/10 - return std::string(); + return name_; } uint64_t ModuleSnapshotMinidump::Address() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - NOTREACHED(); // https://crashpad.chromium.org/bug/10 - return 0; + return minidump_module_.BaseOfImage; } uint64_t ModuleSnapshotMinidump::Size() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - NOTREACHED(); // https://crashpad.chromium.org/bug/10 - return 0; + return minidump_module_.SizeOfImage; } time_t ModuleSnapshotMinidump::Timestamp() const { - INITIALIZATION_STATE_DCHECK_VALID(initialized_); - NOTREACHED(); // https://crashpad.chromium.org/bug/10 - return 0; + return minidump_module_.TimeDateStamp; } void ModuleSnapshotMinidump::FileVersion(uint16_t* version_0, @@ -86,11 +151,12 @@ void ModuleSnapshotMinidump::FileVersion(uint16_t* version_0, uint16_t* version_2, uint16_t* version_3) const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - NOTREACHED(); // https://crashpad.chromium.org/bug/10 - *version_0 = 0; - *version_1 = 0; - *version_2 = 0; - *version_3 = 0; + uint32_t version_01 = minidump_module_.VersionInfo.dwFileVersionMS; + uint32_t version_23 = minidump_module_.VersionInfo.dwFileVersionLS; + *version_0 = static_cast(version_01 >> 16); + *version_1 = static_cast(version_01 & 0xFFFF); + *version_2 = static_cast(version_23 >> 16); + *version_3 = static_cast(version_23 & 0xFFFF); } void ModuleSnapshotMinidump::SourceVersion(uint16_t* version_0, @@ -98,31 +164,40 @@ void ModuleSnapshotMinidump::SourceVersion(uint16_t* version_0, uint16_t* version_2, uint16_t* version_3) const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - NOTREACHED(); // https://crashpad.chromium.org/bug/10 - *version_0 = 0; - *version_1 = 0; - *version_2 = 0; - *version_3 = 0; + uint32_t version_01 = minidump_module_.VersionInfo.dwProductVersionMS; + uint32_t version_23 = minidump_module_.VersionInfo.dwProductVersionLS; + *version_0 = static_cast(version_01 >> 16); + *version_1 = static_cast(version_01 & 0xFFFF); + *version_2 = static_cast(version_23 >> 16); + *version_3 = static_cast(version_23 & 0xFFFF); } ModuleSnapshot::ModuleType ModuleSnapshotMinidump::GetModuleType() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - NOTREACHED(); // https://crashpad.chromium.org/bug/10 + switch (minidump_module_.VersionInfo.dwFileType) { + case VFT_APP: + return kModuleTypeExecutable; + case VFT_DLL: + return kModuleTypeSharedLibrary; + } return kModuleTypeUnknown; } void ModuleSnapshotMinidump::UUIDAndAge(crashpad::UUID* uuid, uint32_t* age) const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - NOTREACHED(); // https://crashpad.chromium.org/bug/10 - *uuid = crashpad::UUID(); - *age = 0; + *uuid = uuid_; + *age = age_; } std::string ModuleSnapshotMinidump::DebugFileName() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - NOTREACHED(); // https://crashpad.chromium.org/bug/10 - return std::string(); + return debug_file_name_; +} + +std::vector ModuleSnapshotMinidump::BuildID() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return build_id_; } std::vector ModuleSnapshotMinidump::AnnotationsVector() const { @@ -167,7 +242,7 @@ bool ModuleSnapshotMinidump::InitializeModuleCrashpadInfo( MinidumpModuleCrashpadInfo minidump_module_crashpad_info; if (minidump_module_crashpad_info_location->DataSize < - sizeof(minidump_module_crashpad_info)) { + sizeof(minidump_module_crashpad_info)) { LOG(ERROR) << "minidump_module_crashpad_info size mismatch"; return false; } @@ -182,7 +257,7 @@ bool ModuleSnapshotMinidump::InitializeModuleCrashpadInfo( } if (minidump_module_crashpad_info.version != - MinidumpModuleCrashpadInfo::kVersion) { + MinidumpModuleCrashpadInfo::kVersion) { LOG(ERROR) << "minidump_module_crashpad_info version mismatch"; return false; } diff --git a/snapshot/minidump/module_snapshot_minidump.h b/snapshot/minidump/module_snapshot_minidump.h index e9dfa778..023d0d59 100644 --- a/snapshot/minidump/module_snapshot_minidump.h +++ b/snapshot/minidump/module_snapshot_minidump.h @@ -75,6 +75,7 @@ class ModuleSnapshotMinidump final : public ModuleSnapshot { ModuleType GetModuleType() const override; void UUIDAndAge(crashpad::UUID* uuid, uint32_t* age) const override; std::string DebugFileName() const override; + std::vector BuildID() const override; std::vector AnnotationsVector() const override; std::map AnnotationsSimpleMap() const override; std::vector AnnotationObjects() const override; @@ -88,10 +89,19 @@ class ModuleSnapshotMinidump final : public ModuleSnapshot { const MINIDUMP_LOCATION_DESCRIPTOR* minidump_module_crashpad_info_location); + // Initializes data from the CodeView record, which usually points toward + // debug symbols. + bool InitializeModuleCodeView(FileReaderInterface* file_reader); + MINIDUMP_MODULE minidump_module_; std::vector annotations_vector_; std::map annotations_simple_map_; std::vector annotation_objects_; + UUID uuid_; + std::vector build_id_; + std::string name_; + std::string debug_file_name_; + uint32_t age_; InitializationStateDcheck initialized_; DISALLOW_COPY_AND_ASSIGN(ModuleSnapshotMinidump); diff --git a/snapshot/minidump/process_snapshot_minidump.cc b/snapshot/minidump/process_snapshot_minidump.cc index 652e0e4c..6a032f90 100644 --- a/snapshot/minidump/process_snapshot_minidump.cc +++ b/snapshot/minidump/process_snapshot_minidump.cc @@ -16,27 +16,55 @@ #include +#include "base/strings/utf_string_conversions.h" +#include "minidump/minidump_extensions.h" +#include "snapshot/memory_map_region_snapshot.h" #include "snapshot/minidump/minidump_simple_string_dictionary_reader.h" #include "util/file/file_io.h" namespace crashpad { +namespace internal { + +class MemoryMapRegionSnapshotMinidump : public MemoryMapRegionSnapshot { + public: + MemoryMapRegionSnapshotMinidump(MINIDUMP_MEMORY_INFO info) : info_(info) {} + ~MemoryMapRegionSnapshotMinidump() override = default; + + const MINIDUMP_MEMORY_INFO& AsMinidumpMemoryInfo() const override { + return info_; + } + + private: + MINIDUMP_MEMORY_INFO info_; +}; + +} // namespace internal + ProcessSnapshotMinidump::ProcessSnapshotMinidump() : ProcessSnapshot(), header_(), stream_directory_(), stream_map_(), modules_(), + threads_(), unloaded_modules_(), + mem_regions_(), + mem_regions_exposed_(), + custom_streams_(), crashpad_info_(), + system_snapshot_(), + exception_snapshot_(), + arch_(CPUArchitecture::kCPUArchitectureUnknown), annotations_simple_map_(), file_reader_(nullptr), - process_id_(static_cast(-1)), - initialized_() { -} + process_id_(kInvalidProcessID), + create_time_(0), + user_time_(0), + kernel_time_(0), + initialized_() {} -ProcessSnapshotMinidump::~ProcessSnapshotMinidump() { -} +ProcessSnapshotMinidump::~ProcessSnapshotMinidump() {} bool ProcessSnapshotMinidump::Initialize(FileReaderInterface* file_reader) { INITIALIZATION_STATE_SET_INITIALIZING(initialized_); @@ -84,9 +112,10 @@ bool ProcessSnapshotMinidump::Initialize(FileReaderInterface* file_reader) { stream_map_[stream_type] = &directory.Location; } - if (!InitializeCrashpadInfo() || - !InitializeMiscInfo() || - !InitializeModules()) { + if (!InitializeCrashpadInfo() || !InitializeMiscInfo() || + !InitializeModules() || !InitializeSystemSnapshot() || + !InitializeMemoryInfo() || !InitializeThreads() || + !InitializeCustomMinidumpStreams() || !InitializeExceptionSnapshot()) { return false; } @@ -94,12 +123,12 @@ bool ProcessSnapshotMinidump::Initialize(FileReaderInterface* file_reader) { return true; } -pid_t ProcessSnapshotMinidump::ProcessID() const { +crashpad::ProcessID ProcessSnapshotMinidump::ProcessID() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return process_id_; } -pid_t ProcessSnapshotMinidump::ParentProcessID() const { +crashpad::ProcessID ProcessSnapshotMinidump::ParentProcessID() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); NOTREACHED(); // https://crashpad.chromium.org/bug/10 return 0; @@ -107,25 +136,22 @@ pid_t ProcessSnapshotMinidump::ParentProcessID() const { void ProcessSnapshotMinidump::SnapshotTime(timeval* snapshot_time) const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - NOTREACHED(); // https://crashpad.chromium.org/bug/10 - snapshot_time->tv_sec = 0; + snapshot_time->tv_sec = header_.TimeDateStamp; snapshot_time->tv_usec = 0; } void ProcessSnapshotMinidump::ProcessStartTime(timeval* start_time) const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - NOTREACHED(); // https://crashpad.chromium.org/bug/10 - start_time->tv_sec = 0; + start_time->tv_sec = create_time_; start_time->tv_usec = 0; } void ProcessSnapshotMinidump::ProcessCPUTimes(timeval* user_time, timeval* system_time) const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - NOTREACHED(); // https://crashpad.chromium.org/bug/10 - user_time->tv_sec = 0; + user_time->tv_sec = user_time_; user_time->tv_usec = 0; - system_time->tv_sec = 0; + system_time->tv_sec = kernel_time_; system_time->tv_usec = 0; } @@ -153,14 +179,16 @@ ProcessSnapshotMinidump::AnnotationsSimpleMap() const { const SystemSnapshot* ProcessSnapshotMinidump::System() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - NOTREACHED(); // https://crashpad.chromium.org/bug/10 - return nullptr; + return &system_snapshot_; } std::vector ProcessSnapshotMinidump::Threads() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - NOTREACHED(); // https://crashpad.chromium.org/bug/10 - return std::vector(); + std::vector threads; + for (const auto& thread : threads_) { + threads.push_back(thread.get()); + } + return threads; } std::vector ProcessSnapshotMinidump::Modules() const { @@ -181,15 +209,17 @@ std::vector ProcessSnapshotMinidump::UnloadedModules() const ExceptionSnapshot* ProcessSnapshotMinidump::Exception() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - NOTREACHED(); // https://crashpad.chromium.org/bug/10 + if (exception_snapshot_.IsValid()) { + return &exception_snapshot_; + } + // Allow caller to know whether the minidump contained an exception stream. return nullptr; } std::vector ProcessSnapshotMinidump::MemoryMap() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - NOTREACHED(); // https://crashpad.chromium.org/bug/10 - return std::vector(); + return mem_regions_exposed_; } std::vector ProcessSnapshotMinidump::Handles() const { @@ -205,6 +235,23 @@ std::vector ProcessSnapshotMinidump::ExtraMemory() return std::vector(); } +const ProcessMemory* ProcessSnapshotMinidump::Memory() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return nullptr; +} + +std::vector +ProcessSnapshotMinidump::CustomMinidumpStreams() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + std::vector result; + result.reserve(custom_streams_.size()); + for (const auto& custom_stream : custom_streams_) { + result.push_back(custom_stream.get()); + } + return result; +} + bool ProcessSnapshotMinidump::InitializeCrashpadInfo() { const auto& stream_it = stream_map_.find(kMinidumpStreamTypeCrashpadInfo); if (stream_it == stream_map_.end()) { @@ -263,12 +310,18 @@ bool ProcessSnapshotMinidump::InitializeMiscInfo() { switch (stream_it->second->DataSize) { case sizeof(MINIDUMP_MISC_INFO_5): case sizeof(MINIDUMP_MISC_INFO_4): + full_version_ = base::UTF16ToUTF8(info.BuildString); + full_version_ = full_version_.substr(0, full_version_.find(";")); + FALLTHROUGH; case sizeof(MINIDUMP_MISC_INFO_3): case sizeof(MINIDUMP_MISC_INFO_2): case sizeof(MINIDUMP_MISC_INFO): // TODO(jperaza): Save the remaining misc info. // https://crashpad.chromium.org/bug/10 process_id_ = info.ProcessId; + create_time_ = info.ProcessCreateTime; + user_time_ = info.ProcessUserTime; + kernel_time_ = info.ProcessKernelTime; } return true; @@ -300,7 +353,7 @@ bool ProcessSnapshotMinidump::InitializeModules() { } if (sizeof(MINIDUMP_MODULE_LIST) + module_count * sizeof(MINIDUMP_MODULE) != - stream_it->second->DataSize) { + stream_it->second->DataSize) { LOG(ERROR) << "module_list size mismatch"; return false; } @@ -342,7 +395,7 @@ bool ProcessSnapshotMinidump::InitializeModulesCrashpadInfo( } if (crashpad_info_.module_list.DataSize < - sizeof(MinidumpModuleCrashpadInfoList)) { + sizeof(MinidumpModuleCrashpadInfoList)) { LOG(ERROR) << "module_crashpad_info_list size mismatch"; return false; } @@ -358,8 +411,8 @@ bool ProcessSnapshotMinidump::InitializeModulesCrashpadInfo( } if (crashpad_info_.module_list.DataSize != - sizeof(MinidumpModuleCrashpadInfoList) + - crashpad_module_count * sizeof(MinidumpModuleCrashpadInfoLink)) { + sizeof(MinidumpModuleCrashpadInfoList) + + crashpad_module_count * sizeof(MinidumpModuleCrashpadInfoLink)) { LOG(ERROR) << "module_crashpad_info_list size mismatch"; return false; } @@ -379,7 +432,8 @@ bool ProcessSnapshotMinidump::InitializeModulesCrashpadInfo( minidump_links[crashpad_module_index]; if (!module_crashpad_info_links ->insert(std::make_pair(minidump_link.minidump_module_list_index, - minidump_link.location)).second) { + minidump_link.location)) + .second) { LOG(WARNING) << "duplicate module_crashpad_info_list minidump_module_list_index " << minidump_link.minidump_module_list_index; @@ -390,4 +444,164 @@ bool ProcessSnapshotMinidump::InitializeModulesCrashpadInfo( return true; } +bool ProcessSnapshotMinidump::InitializeMemoryInfo() { + const auto& stream_it = stream_map_.find(kMinidumpStreamTypeMemoryInfoList); + if (stream_it == stream_map_.end()) { + return true; + } + + if (stream_it->second->DataSize < sizeof(MINIDUMP_MEMORY_INFO_LIST)) { + LOG(ERROR) << "memory_info_list size mismatch"; + return false; + } + + if (!file_reader_->SeekSet(stream_it->second->Rva)) { + return false; + } + + MINIDUMP_MEMORY_INFO_LIST list; + + if (!file_reader_->ReadExactly(&list, sizeof(list))) { + return false; + } + + if (list.SizeOfHeader != sizeof(list)) { + return false; + } + + if (list.SizeOfEntry != sizeof(MINIDUMP_MEMORY_INFO)) { + return false; + } + + if (sizeof(MINIDUMP_MEMORY_INFO_LIST) + + list.NumberOfEntries * list.SizeOfEntry != + stream_it->second->DataSize) { + LOG(ERROR) << "memory_info_list size mismatch"; + return false; + } + + for (uint32_t i = 0; i < list.NumberOfEntries; i++) { + MINIDUMP_MEMORY_INFO info; + + if (!file_reader_->ReadExactly(&info, sizeof(info))) { + return false; + } + + mem_regions_.emplace_back( + std::make_unique(info)); + mem_regions_exposed_.emplace_back(mem_regions_.back().get()); + } + + return true; +} + +bool ProcessSnapshotMinidump::InitializeThreads() { + const auto& stream_it = stream_map_.find(kMinidumpStreamTypeThreadList); + if (stream_it == stream_map_.end()) { + return true; + } + + if (stream_it->second->DataSize < sizeof(MINIDUMP_THREAD_LIST)) { + LOG(ERROR) << "thread_list size mismatch"; + return false; + } + + if (!file_reader_->SeekSet(stream_it->second->Rva)) { + return false; + } + + uint32_t thread_count; + if (!file_reader_->ReadExactly(&thread_count, sizeof(thread_count))) { + return false; + } + + if (sizeof(MINIDUMP_THREAD_LIST) + thread_count * sizeof(MINIDUMP_THREAD) != + stream_it->second->DataSize) { + LOG(ERROR) << "thread_list size mismatch"; + return false; + } + + for (uint32_t thread_index = 0; thread_index < thread_count; ++thread_index) { + const RVA thread_rva = stream_it->second->Rva + sizeof(thread_count) + + thread_index * sizeof(MINIDUMP_THREAD); + + auto thread = std::make_unique(); + if (!thread->Initialize(file_reader_, thread_rva, arch_)) { + return false; + } + + threads_.push_back(std::move(thread)); + } + + return true; +} + +bool ProcessSnapshotMinidump::InitializeSystemSnapshot() { + const auto& stream_it = stream_map_.find(kMinidumpStreamTypeSystemInfo); + if (stream_it == stream_map_.end()) { + return true; + } + + if (stream_it->second->DataSize < sizeof(MINIDUMP_SYSTEM_INFO)) { + LOG(ERROR) << "system info size mismatch"; + return false; + } + + if (!system_snapshot_.Initialize( + file_reader_, stream_it->second->Rva, full_version_)) { + return false; + } + + arch_ = system_snapshot_.GetCPUArchitecture(); + return true; +} + +bool ProcessSnapshotMinidump::InitializeCustomMinidumpStreams() { + for (size_t i = 0; i < stream_directory_.size(); i++) { + const auto& stream = stream_directory_[i]; + + // Filter out reserved minidump and crashpad streams. + const uint32_t stream_type = stream.StreamType; + if (stream_type <= + MinidumpStreamType::kMinidumpStreamTypeLastReservedStream || + (stream_type >= MinidumpStreamType::kMinidumpStreamTypeCrashpadInfo && + stream_type <= MinidumpStreamType:: + kMinidumpStreamTypeCrashpadLastReservedStream)) { + continue; + } + + std::vector data(stream.Location.DataSize); + if (!file_reader_->SeekSet(stream.Location.Rva) || + !file_reader_->ReadExactly(data.data(), data.size())) { + LOG(ERROR) << "Failed to read stream with ID 0x" << std::hex + << stream_type << std::dec << " at index " << i; + return false; + } + + custom_streams_.push_back( + std::make_unique(stream_type, std::move(data))); + } + + return true; +} + +bool ProcessSnapshotMinidump::InitializeExceptionSnapshot() { + const auto& stream_it = stream_map_.find(kMinidumpStreamTypeException); + if (stream_it == stream_map_.end()) { + return true; + } + + if (stream_it->second->DataSize < sizeof(MINIDUMP_EXCEPTION_STREAM)) { + LOG(ERROR) << "system info size mismatch"; + return false; + } + + if (!exception_snapshot_.Initialize( + file_reader_, arch_, stream_it->second->Rva)) { + return false; + } + + return true; +} + } // namespace crashpad diff --git a/snapshot/minidump/process_snapshot_minidump.h b/snapshot/minidump/process_snapshot_minidump.h index 9ba5d9c6..cf2d82bd 100644 --- a/snapshot/minidump/process_snapshot_minidump.h +++ b/snapshot/minidump/process_snapshot_minidump.h @@ -29,7 +29,11 @@ #include "minidump/minidump_extensions.h" #include "snapshot/exception_snapshot.h" #include "snapshot/memory_snapshot.h" +#include "snapshot/minidump/exception_snapshot_minidump.h" +#include "snapshot/minidump/minidump_stream.h" #include "snapshot/minidump/module_snapshot_minidump.h" +#include "snapshot/minidump/system_snapshot_minidump.h" +#include "snapshot/minidump/thread_snapshot_minidump.h" #include "snapshot/module_snapshot.h" #include "snapshot/process_snapshot.h" #include "snapshot/system_snapshot.h" @@ -38,9 +42,14 @@ #include "util/file/file_reader.h" #include "util/misc/initialization_state_dcheck.h" #include "util/misc/uuid.h" +#include "util/process/process_id.h" namespace crashpad { +namespace internal { +class MemoryMapRegionSnapshotMinidump; +} // namespace internal + //! \brief A ProcessSnapshot based on a minidump file. class ProcessSnapshotMinidump final : public ProcessSnapshot { public: @@ -58,8 +67,8 @@ class ProcessSnapshotMinidump final : public ProcessSnapshot { // ProcessSnapshot: - pid_t ProcessID() const override; - pid_t ParentProcessID() const override; + crashpad::ProcessID ProcessID() const override; + crashpad::ProcessID ParentProcessID() const override; void SnapshotTime(timeval* snapshot_time) const override; void ProcessStartTime(timeval* start_time) const override; void ProcessCPUTimes(timeval* user_time, timeval* system_time) const override; @@ -75,6 +84,17 @@ class ProcessSnapshotMinidump final : public ProcessSnapshot { std::vector MemoryMap() const override; std::vector Handles() const override; std::vector ExtraMemory() const override; + const ProcessMemory* Memory() const override; + + //! \brief Returns a list of custom minidump streams. This routine is the + //! equivalent of ModuleSnapshot::CustomMinidumpStreams(), except that in + //! a minidump it is impossible to associate a custom stream to a specific + //! module. + //! + //! \return The caller does not take ownership of the returned objects, they + //! are scoped to the lifetime of the ProcessSnapshotMinidump object that + //! they were obtained from. + std::vector CustomMinidumpStreams() const; private: // Initializes data carried in a MinidumpCrashpadInfo stream on behalf of @@ -85,6 +105,18 @@ class ProcessSnapshotMinidump final : public ProcessSnapshot { // Initialize(). bool InitializeModules(); + // Initializes data carried in a MINIDUMP_THREAD_LIST stream on behalf of + // Initialize(). + bool InitializeThreads(); + + // Initializes data carried in a MINIDUMP_MEMORY_INFO_LIST stream on behalf of + // Initialize(). + bool InitializeMemoryInfo(); + + // Initializes data carried in a MINIDUMP_SYSTEM_INFO stream on behalf of + // Initialize(). + bool InitializeSystemSnapshot(); + // Initializes data carried in a MinidumpModuleCrashpadInfoList structure on // behalf of InitializeModules(). This makes use of MinidumpCrashpadInfo as // well, so it must be called after InitializeCrashpadInfo(). @@ -96,15 +128,34 @@ class ProcessSnapshotMinidump final : public ProcessSnapshot { // Initialize(). bool InitializeMiscInfo(); + // Initializes custom minidump streams. + bool InitializeCustomMinidumpStreams(); + + // Initializes data carried in a MINIDUMP_EXCEPTION_STREAM stream on behalf of + // Initialize(). + bool InitializeExceptionSnapshot(); + MINIDUMP_HEADER header_; std::vector stream_directory_; std::map stream_map_; std::vector> modules_; + std::vector> threads_; std::vector unloaded_modules_; + std::vector> + mem_regions_; + std::vector mem_regions_exposed_; + std::vector> custom_streams_; MinidumpCrashpadInfo crashpad_info_; + internal::SystemSnapshotMinidump system_snapshot_; + internal::ExceptionSnapshotMinidump exception_snapshot_; + CPUArchitecture arch_; std::map annotations_simple_map_; + std::string full_version_; FileReaderInterface* file_reader_; // weak - pid_t process_id_; + crashpad::ProcessID process_id_; + uint32_t create_time_; + uint32_t user_time_; + uint32_t kernel_time_; InitializationStateDcheck initialized_; DISALLOW_COPY_AND_ASSIGN(ProcessSnapshotMinidump); diff --git a/snapshot/minidump/process_snapshot_minidump_test.cc b/snapshot/minidump/process_snapshot_minidump_test.cc index ed179394..9794979c 100644 --- a/snapshot/minidump/process_snapshot_minidump_test.cc +++ b/snapshot/minidump/process_snapshot_minidump_test.cc @@ -18,17 +18,72 @@ #include #include +#include #include +#include "base/numerics/safe_math.h" +#include "base/stl_util.h" +#include "base/strings/utf_string_conversions.h" #include "gtest/gtest.h" +#include "minidump/minidump_context.h" +#include "snapshot/memory_map_region_snapshot.h" #include "snapshot/minidump/minidump_annotation_reader.h" #include "snapshot/module_snapshot.h" #include "util/file/string_file.h" +#include "util/misc/pdb_structures.h" namespace crashpad { namespace test { namespace { +class ReadToVector : public crashpad::MemorySnapshot::Delegate { + public: + std::vector result; + + bool MemorySnapshotDelegateRead(void* data, size_t size) override { + result.resize(size); + memcpy(result.data(), data, size); + return true; + } +}; + +MinidumpContextARM64 GetArm64MinidumpContext() { + MinidumpContextARM64 minidump_context; + + minidump_context.context_flags = kMinidumpContextARM64Full; + + minidump_context.cpsr = 0; + + for (int i = 0; i < 29; i++) { + minidump_context.regs[i] = i + 1; + } + + minidump_context.fp = 30; + minidump_context.lr = 31; + minidump_context.sp = 32; + minidump_context.pc = 33; + + for (int i = 0; i < 32; i++) { + minidump_context.fpsimd[i].lo = i * 2 + 34; + minidump_context.fpsimd[i].hi = i * 2 + 35; + } + + minidump_context.fpcr = 98; + minidump_context.fpsr = 99; + + for (int i = 0; i < 8; i++) { + minidump_context.bcr[i] = i * 2 + 100; + minidump_context.bvr[i] = i * 2 + 101; + } + + for (int i = 0; i < 2; i++) { + minidump_context.wcr[i] = i * 2 + 115; + minidump_context.wvr[i] = i * 2 + 116; + } + + return minidump_context; +} + TEST(ProcessSnapshotMinidump, EmptyFile) { StringFile string_file; ProcessSnapshotMinidump process_snapshot; @@ -287,7 +342,55 @@ TEST(ProcessSnapshotMinidump, Modules) { EXPECT_TRUE(string_file.Write(&header, sizeof(header))); MINIDUMP_MODULE minidump_module = {}; - uint32_t minidump_module_count = 4; + constexpr uint32_t minidump_module_count = 4; + RVA name_rvas[minidump_module_count]; + std::string names[minidump_module_count] = { + "libtacotruck", + "libevidencebased", + "libgeorgism", + "librealistutopia", + }; + + minidump_module.BaseOfImage = 0xbadf00d; + minidump_module.SizeOfImage = 9001; + minidump_module.TimeDateStamp = 1970; + minidump_module.VersionInfo.dwFileVersionMS = 0xAABBCCDD; + minidump_module.VersionInfo.dwFileVersionLS = 0xEEFF4242; + minidump_module.VersionInfo.dwProductVersionMS = 0xAAAABBBB; + minidump_module.VersionInfo.dwProductVersionLS = 0xCCCCDDDD; + minidump_module.VersionInfo.dwFileType = VFT_APP; + + for (uint32_t i = 0; i < minidump_module_count; i++) { + name_rvas[i] = static_cast(string_file.SeekGet()); + auto name16 = base::UTF8ToUTF16(names[i]); + uint32_t size = + base::checked_cast(sizeof(name16[0]) * name16.size()); + EXPECT_TRUE(string_file.Write(&size, sizeof(size))); + EXPECT_TRUE(string_file.Write(&name16[0], size)); + } + + CodeViewRecordPDB70 pdb70_cv; + pdb70_cv.signature = CodeViewRecordPDB70::kSignature; + pdb70_cv.age = 7; + pdb70_cv.uuid.InitializeFromString("00112233-4455-6677-8899-aabbccddeeff"); + pdb70_cv.pdb_name[0] = '\0'; + + auto pdb70_loc = static_cast(string_file.SeekGet()); + auto pdb70_size = sizeof(pdb70_cv); + + EXPECT_TRUE(string_file.Write(&pdb70_cv, sizeof(pdb70_cv))); + + CodeViewRecordBuildID build_id_cv; + build_id_cv.signature = CodeViewRecordBuildID::kSignature; + + auto build_id_cv_loc = static_cast(string_file.SeekGet()); + + EXPECT_TRUE(string_file.Write(&build_id_cv, + offsetof(CodeViewRecordBuildID, build_id))); + EXPECT_TRUE(string_file.Write("atestbuildidbecausewhynot", 25)); + + auto build_id_cv_size = + static_cast(string_file.SeekGet() - build_id_cv_loc); MINIDUMP_DIRECTORY minidump_module_list_directory = {}; minidump_module_list_directory.StreamType = kMinidumpStreamTypeModuleList; @@ -302,7 +405,18 @@ TEST(ProcessSnapshotMinidump, Modules) { for (uint32_t minidump_module_index = 0; minidump_module_index < minidump_module_count; ++minidump_module_index) { + if (minidump_module_index % 2) { + minidump_module.CvRecord.Rva = pdb70_loc; + minidump_module.CvRecord.DataSize = static_cast(pdb70_size); + } else { + minidump_module.CvRecord.Rva = build_id_cv_loc; + minidump_module.CvRecord.DataSize = + static_cast(build_id_cv_size); + } + + minidump_module.ModuleNameRva = name_rvas[minidump_module_index]; EXPECT_TRUE(string_file.Write(&minidump_module, sizeof(minidump_module))); + minidump_module.TimeDateStamp++; } MinidumpModuleCrashpadInfo crashpad_module_0 = {}; @@ -398,6 +512,47 @@ TEST(ProcessSnapshotMinidump, Modules) { std::vector modules = process_snapshot.Modules(); ASSERT_EQ(modules.size(), minidump_module_count); + for (uint32_t i = 0; i < minidump_module_count; i++) { + EXPECT_EQ(modules[i]->Name(), names[i]); + EXPECT_EQ(modules[i]->Address(), 0xbadf00dU); + EXPECT_EQ(modules[i]->Size(), 9001U); + EXPECT_EQ(modules[i]->Timestamp(), static_cast(1970U + i)); + + uint16_t v0; + uint16_t v1; + uint16_t v2; + uint16_t v3; + + modules[i]->FileVersion(&v0, &v1, &v2, &v3); + EXPECT_EQ(v0, 0xAABBU); + EXPECT_EQ(v1, 0xCCDDU); + EXPECT_EQ(v2, 0xEEFFU); + EXPECT_EQ(v3, 0x4242U); + + modules[i]->SourceVersion(&v0, &v1, &v2, &v3); + EXPECT_EQ(v0, 0xAAAAU); + EXPECT_EQ(v1, 0xBBBBU); + EXPECT_EQ(v2, 0xCCCCU); + EXPECT_EQ(v3, 0xDDDDU); + + EXPECT_EQ(modules[i]->GetModuleType(), + ModuleSnapshot::kModuleTypeExecutable); + + if (i % 2) { + uint32_t age; + UUID uuid; + modules[i]->UUIDAndAge(&uuid, &age); + + EXPECT_EQ(uuid.ToString(), "00112233-4455-6677-8899-aabbccddeeff"); + EXPECT_EQ(age, 7U); + } else { + auto build_id = modules[i]->BuildID(); + std::string build_id_text(build_id.data(), + build_id.data() + build_id.size()); + EXPECT_EQ(build_id_text, "atestbuildidbecausewhynot"); + } + } + auto annotations_simple_map = modules[0]->AnnotationsSimpleMap(); EXPECT_EQ(annotations_simple_map, dictionary_0); @@ -426,7 +581,7 @@ TEST(ProcessSnapshotMinidump, ProcessID) { MINIDUMP_HEADER header = {}; ASSERT_TRUE(string_file.Write(&header, sizeof(header))); - static const pid_t kTestProcessId = 42; + static const crashpad::ProcessID kTestProcessId = 42; MINIDUMP_MISC_INFO misc_info = {}; misc_info.SizeOfInfo = sizeof(misc_info); misc_info.Flags1 = MINIDUMP_MISC1_PROCESS_ID; @@ -452,6 +607,827 @@ TEST(ProcessSnapshotMinidump, ProcessID) { EXPECT_EQ(process_snapshot.ProcessID(), kTestProcessId); } +TEST(ProcessSnapshotMinidump, SnapshotTime) { + StringFile string_file; + + MINIDUMP_HEADER header = {}; + header.Signature = MINIDUMP_SIGNATURE; + header.Version = MINIDUMP_VERSION; + header.TimeDateStamp = 42; + ASSERT_TRUE(string_file.Write(&header, sizeof(header))); + + ProcessSnapshotMinidump process_snapshot; + ASSERT_TRUE(process_snapshot.Initialize(&string_file)); + + timeval snapshot_time; + process_snapshot.SnapshotTime(&snapshot_time); + EXPECT_EQ(snapshot_time.tv_sec, 42); + EXPECT_EQ(snapshot_time.tv_usec, 0); +} + +TEST(ProcessSnapshotMinidump, MiscTimes) { + StringFile string_file; + + MINIDUMP_HEADER header = {}; + ASSERT_TRUE(string_file.Write(&header, sizeof(header))); + + MINIDUMP_MISC_INFO misc_info = {}; + misc_info.SizeOfInfo = sizeof(misc_info); + misc_info.Flags1 = MINIDUMP_MISC1_PROCESS_TIMES; + misc_info.ProcessCreateTime = 42; + misc_info.ProcessUserTime = 43; + misc_info.ProcessKernelTime = 44; + + MINIDUMP_DIRECTORY misc_directory = {}; + misc_directory.StreamType = kMinidumpStreamTypeMiscInfo; + misc_directory.Location.DataSize = sizeof(misc_info); + misc_directory.Location.Rva = static_cast(string_file.SeekGet()); + ASSERT_TRUE(string_file.Write(&misc_info, sizeof(misc_info))); + + header.StreamDirectoryRva = static_cast(string_file.SeekGet()); + ASSERT_TRUE(string_file.Write(&misc_directory, sizeof(misc_directory))); + + header.Signature = MINIDUMP_SIGNATURE; + header.Version = MINIDUMP_VERSION; + header.NumberOfStreams = 1; + ASSERT_TRUE(string_file.SeekSet(0)); + ASSERT_TRUE(string_file.Write(&header, sizeof(header))); + + ProcessSnapshotMinidump process_snapshot; + ASSERT_TRUE(process_snapshot.Initialize(&string_file)); + + timeval start_time, user_time, kernel_time; + process_snapshot.ProcessStartTime(&start_time); + process_snapshot.ProcessCPUTimes(&user_time, &kernel_time); + EXPECT_EQ(static_cast(start_time.tv_sec), + misc_info.ProcessCreateTime); + EXPECT_EQ(start_time.tv_usec, 0); + EXPECT_EQ(static_cast(user_time.tv_sec), misc_info.ProcessUserTime); + EXPECT_EQ(user_time.tv_usec, 0); + EXPECT_EQ(static_cast(kernel_time.tv_sec), + misc_info.ProcessKernelTime); + EXPECT_EQ(kernel_time.tv_usec, 0); +} + +TEST(ProcessSnapshotMinidump, Threads) { + StringFile string_file; + + MINIDUMP_HEADER header = {}; + EXPECT_TRUE(string_file.Write(&header, sizeof(header))); + + MINIDUMP_THREAD minidump_thread = {}; + uint32_t minidump_thread_count = 4; + + minidump_thread.ThreadId = 42; + minidump_thread.Teb = 24; + + MINIDUMP_DIRECTORY minidump_thread_list_directory = {}; + minidump_thread_list_directory.StreamType = kMinidumpStreamTypeThreadList; + minidump_thread_list_directory.Location.DataSize = + sizeof(MINIDUMP_THREAD_LIST) + + minidump_thread_count * sizeof(MINIDUMP_THREAD); + minidump_thread_list_directory.Location.Rva = + static_cast(string_file.SeekGet()); + + // Fields in MINIDUMP_THREAD_LIST. + EXPECT_TRUE( + string_file.Write(&minidump_thread_count, sizeof(minidump_thread_count))); + for (uint32_t minidump_thread_index = 0; + minidump_thread_index < minidump_thread_count; + ++minidump_thread_index) { + EXPECT_TRUE(string_file.Write(&minidump_thread, sizeof(minidump_thread))); + minidump_thread.ThreadId++; + } + + header.StreamDirectoryRva = static_cast(string_file.SeekGet()); + EXPECT_TRUE(string_file.Write(&minidump_thread_list_directory, + sizeof(minidump_thread_list_directory))); + + header.Signature = MINIDUMP_SIGNATURE; + header.Version = MINIDUMP_VERSION; + header.NumberOfStreams = 1; + EXPECT_TRUE(string_file.SeekSet(0)); + EXPECT_TRUE(string_file.Write(&header, sizeof(header))); + + ProcessSnapshotMinidump process_snapshot; + EXPECT_TRUE(process_snapshot.Initialize(&string_file)); + + std::vector threads = process_snapshot.Threads(); + ASSERT_EQ(threads.size(), minidump_thread_count); + + uint32_t thread_id = 42; + for (const auto& thread : threads) { + EXPECT_EQ(thread->ThreadID(), thread_id); + EXPECT_EQ(thread->ThreadSpecificDataAddress(), 24UL); + thread_id++; + } +} + +TEST(ProcessSnapshotMinidump, System) { + const char* cpu_info = "GenuineIntel"; + const uint32_t* cpu_info_bytes = reinterpret_cast(cpu_info); + StringFile string_file; + + MINIDUMP_HEADER header = {}; + EXPECT_TRUE(string_file.Write(&header, sizeof(header))); + + MINIDUMP_SYSTEM_INFO minidump_system_info = {}; + + minidump_system_info.ProcessorArchitecture = kMinidumpCPUArchitectureX86; + minidump_system_info.ProcessorLevel = 3; + minidump_system_info.ProcessorRevision = 3; + minidump_system_info.NumberOfProcessors = 8; + minidump_system_info.ProductType = kMinidumpOSTypeServer; + minidump_system_info.PlatformId = kMinidumpOSFuchsia; + minidump_system_info.MajorVersion = 3; + minidump_system_info.MinorVersion = 4; + minidump_system_info.BuildNumber = 56; + minidump_system_info.CSDVersionRva = WriteString(&string_file, "Snazzle"); + minidump_system_info.Cpu.X86CpuInfo.VendorId[0] = cpu_info_bytes[0]; + minidump_system_info.Cpu.X86CpuInfo.VendorId[1] = cpu_info_bytes[1]; + minidump_system_info.Cpu.X86CpuInfo.VendorId[2] = cpu_info_bytes[2]; + + MINIDUMP_MISC_INFO_5 minidump_misc_info = {}; + base::string16 build_string; + ASSERT_TRUE(base::UTF8ToUTF16( + "MyOSVersion; MyMachineDescription", 33, &build_string)); + std::copy(build_string.begin(), build_string.end(), + minidump_misc_info.BuildString); + + MINIDUMP_DIRECTORY minidump_system_info_directory = {}; + minidump_system_info_directory.StreamType = kMinidumpStreamTypeSystemInfo; + minidump_system_info_directory.Location.DataSize = + sizeof(MINIDUMP_SYSTEM_INFO); + minidump_system_info_directory.Location.Rva = + static_cast(string_file.SeekGet()); + + ASSERT_TRUE( + string_file.Write(&minidump_system_info, sizeof(minidump_system_info))); + + MINIDUMP_DIRECTORY minidump_misc_info_directory = {}; + minidump_misc_info_directory.StreamType = kMinidumpStreamTypeMiscInfo; + minidump_misc_info_directory.Location.DataSize = sizeof(MINIDUMP_MISC_INFO_5); + minidump_misc_info_directory.Location.Rva = + static_cast(string_file.SeekGet()); + + ASSERT_TRUE( + string_file.Write(&minidump_misc_info, sizeof(minidump_misc_info))); + + header.StreamDirectoryRva = static_cast(string_file.SeekGet()); + ASSERT_TRUE(string_file.Write(&minidump_system_info_directory, + sizeof(minidump_system_info_directory))); + ASSERT_TRUE(string_file.Write(&minidump_misc_info_directory, + sizeof(minidump_misc_info_directory))); + + header.Signature = MINIDUMP_SIGNATURE; + header.Version = MINIDUMP_VERSION; + header.NumberOfStreams = 2; + EXPECT_TRUE(string_file.SeekSet(0)); + EXPECT_TRUE(string_file.Write(&header, sizeof(header))); + + ProcessSnapshotMinidump process_snapshot; + EXPECT_TRUE(process_snapshot.Initialize(&string_file)); + + const SystemSnapshot* s = process_snapshot.System(); + + EXPECT_EQ(s->GetCPUArchitecture(), kCPUArchitectureX86); + EXPECT_EQ(s->CPURevision(), 3UL); + EXPECT_EQ(s->CPUVendor(), "GenuineIntel"); + EXPECT_EQ(s->GetOperatingSystem(), + SystemSnapshot::OperatingSystem::kOperatingSystemFuchsia); + EXPECT_EQ(s->OSVersionFull(), "MyOSVersion"); + + int major, minor, bugfix; + std::string build; + s->OSVersion(&major, &minor, &bugfix, &build); + + EXPECT_EQ(major, 3); + EXPECT_EQ(minor, 4); + EXPECT_EQ(bugfix, 56); + EXPECT_EQ(build, "Snazzle"); +} + +TEST(ProcessSnapshotMinidump, ThreadContextARM64) { + StringFile string_file; + + MINIDUMP_HEADER header = {}; + EXPECT_TRUE(string_file.Write(&header, sizeof(header))); + + MINIDUMP_SYSTEM_INFO minidump_system_info = {}; + + minidump_system_info.ProcessorArchitecture = kMinidumpCPUArchitectureARM64; + minidump_system_info.ProductType = kMinidumpOSTypeServer; + minidump_system_info.PlatformId = kMinidumpOSFuchsia; + minidump_system_info.CSDVersionRva = WriteString(&string_file, ""); + + MINIDUMP_DIRECTORY minidump_system_info_directory = {}; + minidump_system_info_directory.StreamType = kMinidumpStreamTypeSystemInfo; + minidump_system_info_directory.Location.DataSize = + sizeof(MINIDUMP_SYSTEM_INFO); + minidump_system_info_directory.Location.Rva = + static_cast(string_file.SeekGet()); + + ASSERT_TRUE( + string_file.Write(&minidump_system_info, sizeof(minidump_system_info))); + + MINIDUMP_THREAD minidump_thread = {}; + uint32_t minidump_thread_count = 1; + + minidump_thread.ThreadId = 42; + minidump_thread.Teb = 24; + + MinidumpContextARM64 minidump_context = GetArm64MinidumpContext(); + + minidump_thread.ThreadContext.DataSize = sizeof(minidump_context); + minidump_thread.ThreadContext.Rva = static_cast(string_file.SeekGet()); + + EXPECT_TRUE(string_file.Write(&minidump_context, sizeof(minidump_context))); + + MINIDUMP_DIRECTORY minidump_thread_list_directory = {}; + minidump_thread_list_directory.StreamType = kMinidumpStreamTypeThreadList; + minidump_thread_list_directory.Location.DataSize = + sizeof(MINIDUMP_THREAD_LIST) + + minidump_thread_count * sizeof(MINIDUMP_THREAD); + minidump_thread_list_directory.Location.Rva = + static_cast(string_file.SeekGet()); + + // Fields in MINIDUMP_THREAD_LIST. + EXPECT_TRUE( + string_file.Write(&minidump_thread_count, sizeof(minidump_thread_count))); + EXPECT_TRUE(string_file.Write(&minidump_thread, sizeof(minidump_thread))); + + header.StreamDirectoryRva = static_cast(string_file.SeekGet()); + ASSERT_TRUE(string_file.Write(&minidump_system_info_directory, + sizeof(minidump_system_info_directory))); + ASSERT_TRUE(string_file.Write(&minidump_thread_list_directory, + sizeof(minidump_thread_list_directory))); + + header.Signature = MINIDUMP_SIGNATURE; + header.Version = MINIDUMP_VERSION; + header.NumberOfStreams = 2; + EXPECT_TRUE(string_file.SeekSet(0)); + EXPECT_TRUE(string_file.Write(&header, sizeof(header))); + + ProcessSnapshotMinidump process_snapshot; + EXPECT_TRUE(process_snapshot.Initialize(&string_file)); + + std::vector threads = process_snapshot.Threads(); + ASSERT_EQ(threads.size(), minidump_thread_count); + + const CPUContext* ctx_generic = threads[0]->Context(); + + ASSERT_EQ(ctx_generic->architecture, CPUArchitecture::kCPUArchitectureARM64); + + const CPUContextARM64* ctx = ctx_generic->arm64; + + EXPECT_EQ(ctx->spsr, 0UL); + + for (unsigned int i = 0; i < 31; i++) { + EXPECT_EQ(ctx->regs[i], i + 1); + } + + EXPECT_EQ(ctx->sp, 32UL); + EXPECT_EQ(ctx->pc, 33UL); + EXPECT_EQ(ctx->fpcr, 98UL); + EXPECT_EQ(ctx->fpsr, 99UL); + + for (unsigned int i = 0; i < 32; i++) { + EXPECT_EQ(ctx->fpsimd[i].lo, i * 2 + 34); + EXPECT_EQ(ctx->fpsimd[i].hi, i * 2 + 35); + } +} + +TEST(ProcessSnapshotMinidump, ThreadContextX86_64) { + StringFile string_file; + + MINIDUMP_HEADER header = {}; + EXPECT_TRUE(string_file.Write(&header, sizeof(header))); + + MINIDUMP_SYSTEM_INFO minidump_system_info = {}; + + minidump_system_info.ProcessorArchitecture = kMinidumpCPUArchitectureAMD64; + minidump_system_info.ProductType = kMinidumpOSTypeServer; + minidump_system_info.PlatformId = kMinidumpOSFuchsia; + minidump_system_info.CSDVersionRva = WriteString(&string_file, ""); + + MINIDUMP_DIRECTORY minidump_system_info_directory = {}; + minidump_system_info_directory.StreamType = kMinidumpStreamTypeSystemInfo; + minidump_system_info_directory.Location.DataSize = + sizeof(MINIDUMP_SYSTEM_INFO); + minidump_system_info_directory.Location.Rva = + static_cast(string_file.SeekGet()); + + ASSERT_TRUE( + string_file.Write(&minidump_system_info, sizeof(minidump_system_info))); + + MINIDUMP_THREAD minidump_thread = {}; + uint32_t minidump_thread_count = 1; + + minidump_thread.ThreadId = 42; + minidump_thread.Teb = 24; + + MinidumpContextAMD64 minidump_context; + + minidump_context.context_flags = kMinidumpContextAMD64Full; + + minidump_context.mx_csr = 0; + minidump_context.cs = 1; + minidump_context.ds = 2; + minidump_context.es = 3; + minidump_context.fs = 4; + minidump_context.gs = 5; + minidump_context.ss = 6; + minidump_context.eflags = 7; + minidump_context.dr0 = 8; + minidump_context.dr1 = 9; + minidump_context.dr2 = 10; + minidump_context.dr3 = 11; + minidump_context.dr6 = 12; + minidump_context.dr7 = 13; + minidump_context.rax = 14; + minidump_context.rcx = 15; + minidump_context.rdx = 16; + minidump_context.rbx = 17; + minidump_context.rsp = 18; + minidump_context.rbp = 19; + minidump_context.rsi = 20; + minidump_context.rdi = 21; + minidump_context.r8 = 22; + minidump_context.r9 = 23; + minidump_context.r10 = 24; + minidump_context.r11 = 25; + minidump_context.r12 = 26; + minidump_context.r13 = 27; + minidump_context.r14 = 28; + minidump_context.r15 = 29; + minidump_context.rip = 30; + minidump_context.vector_control = 31; + minidump_context.debug_control = 32; + minidump_context.last_branch_to_rip = 33; + minidump_context.last_branch_from_rip = 34; + minidump_context.last_exception_to_rip = 35; + minidump_context.last_exception_from_rip = 36; + minidump_context.fxsave.fcw = 37; + minidump_context.fxsave.fsw = 38; + minidump_context.fxsave.ftw = 39; + minidump_context.fxsave.reserved_1 = 40; + minidump_context.fxsave.fop = 41; + minidump_context.fxsave.fpu_ip_64 = 42; + minidump_context.fxsave.fpu_dp_64 = 43; + + for (size_t i = 0; i < base::size(minidump_context.vector_register); i++) { + minidump_context.vector_register[i].lo = i * 2 + 44; + minidump_context.vector_register[i].hi = i * 2 + 45; + } + + for (uint8_t i = 0; i < base::size(minidump_context.fxsave.reserved_4); i++) { + minidump_context.fxsave.reserved_4[i] = i * 2 + 115; + minidump_context.fxsave.available[i] = i * 2 + 116; + } + + for (size_t i = 0; i < base::size(minidump_context.fxsave.st_mm); i++) { + for (uint8_t j = 0; + j < base::size(minidump_context.fxsave.st_mm[0].mm_value); + j++) { + minidump_context.fxsave.st_mm[i].mm_value[j] = j + 1; + minidump_context.fxsave.st_mm[i].mm_reserved[j] = j + 1; + } + } + + for (size_t i = 0; i < base::size(minidump_context.fxsave.xmm); i++) { + for (uint8_t j = 0; j < base::size(minidump_context.fxsave.xmm[0]); j++) { + minidump_context.fxsave.xmm[i][j] = j + 1; + } + } + + minidump_thread.ThreadContext.DataSize = sizeof(minidump_context); + minidump_thread.ThreadContext.Rva = static_cast(string_file.SeekGet()); + + EXPECT_TRUE(string_file.Write(&minidump_context, sizeof(minidump_context))); + + MINIDUMP_DIRECTORY minidump_thread_list_directory = {}; + minidump_thread_list_directory.StreamType = kMinidumpStreamTypeThreadList; + minidump_thread_list_directory.Location.DataSize = + sizeof(MINIDUMP_THREAD_LIST) + + minidump_thread_count * sizeof(MINIDUMP_THREAD); + minidump_thread_list_directory.Location.Rva = + static_cast(string_file.SeekGet()); + + // Fields in MINIDUMP_THREAD_LIST. + EXPECT_TRUE( + string_file.Write(&minidump_thread_count, sizeof(minidump_thread_count))); + EXPECT_TRUE(string_file.Write(&minidump_thread, sizeof(minidump_thread))); + + header.StreamDirectoryRva = static_cast(string_file.SeekGet()); + ASSERT_TRUE(string_file.Write(&minidump_system_info_directory, + sizeof(minidump_system_info_directory))); + ASSERT_TRUE(string_file.Write(&minidump_thread_list_directory, + sizeof(minidump_thread_list_directory))); + + header.Signature = MINIDUMP_SIGNATURE; + header.Version = MINIDUMP_VERSION; + header.NumberOfStreams = 2; + EXPECT_TRUE(string_file.SeekSet(0)); + EXPECT_TRUE(string_file.Write(&header, sizeof(header))); + + ProcessSnapshotMinidump process_snapshot; + EXPECT_TRUE(process_snapshot.Initialize(&string_file)); + + std::vector threads = process_snapshot.Threads(); + ASSERT_EQ(threads.size(), minidump_thread_count); + + const CPUContext* ctx_generic = threads[0]->Context(); + + ASSERT_EQ(ctx_generic->architecture, CPUArchitecture::kCPUArchitectureX86_64); + + const CPUContextX86_64* ctx = ctx_generic->x86_64; + EXPECT_EQ(ctx->cs, 1); + EXPECT_EQ(ctx->fs, 4); + EXPECT_EQ(ctx->gs, 5); + EXPECT_EQ(ctx->rflags, 7UL); + EXPECT_EQ(ctx->dr0, 8UL); + EXPECT_EQ(ctx->dr1, 9U); + EXPECT_EQ(ctx->dr2, 10U); + EXPECT_EQ(ctx->dr3, 11U); + EXPECT_EQ(ctx->dr4, 12U); + EXPECT_EQ(ctx->dr5, 13U); + EXPECT_EQ(ctx->dr6, 12U); + EXPECT_EQ(ctx->dr7, 13U); + EXPECT_EQ(ctx->rax, 14U); + EXPECT_EQ(ctx->rcx, 15U); + EXPECT_EQ(ctx->rdx, 16U); + EXPECT_EQ(ctx->rbx, 17U); + EXPECT_EQ(ctx->rsp, 18U); + EXPECT_EQ(ctx->rbp, 19U); + EXPECT_EQ(ctx->rsi, 20U); + EXPECT_EQ(ctx->rdi, 21U); + EXPECT_EQ(ctx->r8, 22U); + EXPECT_EQ(ctx->r9, 23U); + EXPECT_EQ(ctx->r10, 24U); + EXPECT_EQ(ctx->r11, 25U); + EXPECT_EQ(ctx->r12, 26U); + EXPECT_EQ(ctx->r13, 27U); + EXPECT_EQ(ctx->r14, 28U); + EXPECT_EQ(ctx->r15, 29U); + EXPECT_EQ(ctx->rip, 30U); + EXPECT_EQ(ctx->fxsave.fcw, 37U); + EXPECT_EQ(ctx->fxsave.fsw, 38U); + EXPECT_EQ(ctx->fxsave.ftw, 39U); + EXPECT_EQ(ctx->fxsave.reserved_1, 40U); + EXPECT_EQ(ctx->fxsave.fop, 41U); + EXPECT_EQ(ctx->fxsave.fpu_ip_64, 42U); + EXPECT_EQ(ctx->fxsave.fpu_dp_64, 43U); + + for (uint8_t i = 0; i < base::size(ctx->fxsave.reserved_4); i++) { + EXPECT_EQ(ctx->fxsave.reserved_4[i], i * 2 + 115); + EXPECT_EQ(ctx->fxsave.available[i], i * 2 + 116); + } + + for (size_t i = 0; i < base::size(ctx->fxsave.st_mm); i++) { + for (uint8_t j = 0; j < base::size(ctx->fxsave.st_mm[0].mm_value); j++) { + EXPECT_EQ(ctx->fxsave.st_mm[i].mm_value[j], j + 1); + EXPECT_EQ(ctx->fxsave.st_mm[i].mm_reserved[j], j + 1); + } + } + + for (size_t i = 0; i < base::size(ctx->fxsave.xmm); i++) { + for (uint8_t j = 0; j < base::size(ctx->fxsave.xmm[0]); j++) { + EXPECT_EQ(ctx->fxsave.xmm[i][j], j + 1); + } + } +} + +TEST(ProcessSnapshotMinidump, MemoryMap) { + StringFile string_file; + + MINIDUMP_HEADER header = {}; + EXPECT_TRUE(string_file.Write(&header, sizeof(header))); + + MINIDUMP_MEMORY_INFO minidump_memory_info_1 = {}; + MINIDUMP_MEMORY_INFO minidump_memory_info_2 = {}; + uint32_t minidump_memory_info_count = 2; + + minidump_memory_info_1.BaseAddress = 1; + minidump_memory_info_1.AllocationBase = 2; + minidump_memory_info_1.AllocationProtect = 3; + minidump_memory_info_1.RegionSize = 4; + minidump_memory_info_1.State = 5; + minidump_memory_info_1.Protect = 6; + minidump_memory_info_1.Type = 6; + + minidump_memory_info_2.BaseAddress = 7; + minidump_memory_info_2.AllocationBase = 8; + minidump_memory_info_2.AllocationProtect = 9; + minidump_memory_info_2.RegionSize = 10; + minidump_memory_info_2.State = 11; + minidump_memory_info_2.Protect = 12; + minidump_memory_info_2.Type = 13; + + MINIDUMP_MEMORY_INFO_LIST minidump_memory_info_list = {}; + + minidump_memory_info_list.SizeOfHeader = sizeof(minidump_memory_info_list); + minidump_memory_info_list.SizeOfEntry = sizeof(MINIDUMP_MEMORY_INFO); + minidump_memory_info_list.NumberOfEntries = minidump_memory_info_count; + + MINIDUMP_DIRECTORY minidump_memory_info_list_directory = {}; + minidump_memory_info_list_directory.StreamType = + kMinidumpStreamTypeMemoryInfoList; + minidump_memory_info_list_directory.Location.DataSize = + sizeof(minidump_memory_info_list) + + minidump_memory_info_count * sizeof(MINIDUMP_MEMORY_INFO); + minidump_memory_info_list_directory.Location.Rva = + static_cast(string_file.SeekGet()); + + EXPECT_TRUE(string_file.Write(&minidump_memory_info_list, + sizeof(minidump_memory_info_list))); + EXPECT_TRUE(string_file.Write(&minidump_memory_info_1, + sizeof(minidump_memory_info_1))); + EXPECT_TRUE(string_file.Write(&minidump_memory_info_2, + sizeof(minidump_memory_info_2))); + + header.StreamDirectoryRva = static_cast(string_file.SeekGet()); + EXPECT_TRUE(string_file.Write(&minidump_memory_info_list_directory, + sizeof(minidump_memory_info_list_directory))); + + header.Signature = MINIDUMP_SIGNATURE; + header.Version = MINIDUMP_VERSION; + header.NumberOfStreams = 1; + EXPECT_TRUE(string_file.SeekSet(0)); + EXPECT_TRUE(string_file.Write(&header, sizeof(header))); + + ProcessSnapshotMinidump process_snapshot; + EXPECT_TRUE(process_snapshot.Initialize(&string_file)); + + std::vector map = + process_snapshot.MemoryMap(); + ASSERT_EQ(map.size(), minidump_memory_info_count); + EXPECT_EQ(memcmp(&map[0]->AsMinidumpMemoryInfo(), + &minidump_memory_info_1, + sizeof(minidump_memory_info_1)), + 0); + EXPECT_EQ(memcmp(&map[1]->AsMinidumpMemoryInfo(), + &minidump_memory_info_2, + sizeof(minidump_memory_info_2)), + 0); +} + +TEST(ProcessSnapshotMinidump, Stacks) { + StringFile string_file; + + MINIDUMP_HEADER header = {}; + EXPECT_TRUE(string_file.Write(&header, sizeof(header))); + + MINIDUMP_THREAD minidump_thread = {}; + uint32_t minidump_thread_count = 1; + + minidump_thread.ThreadId = 42; + minidump_thread.Stack.StartOfMemoryRange = 0xbeefd00d; + + std::vector minidump_stack = {'1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', + 'a', + 'b', + 'c', + 'd', + 'e', + 'f'}; + + minidump_thread.Stack.Memory.DataSize = + base::checked_cast(minidump_stack.size()); + minidump_thread.Stack.Memory.Rva = static_cast(string_file.SeekGet()); + + EXPECT_TRUE(string_file.Write(minidump_stack.data(), minidump_stack.size())); + + MINIDUMP_DIRECTORY minidump_thread_list_directory = {}; + minidump_thread_list_directory.StreamType = kMinidumpStreamTypeThreadList; + minidump_thread_list_directory.Location.DataSize = + sizeof(MINIDUMP_THREAD_LIST) + + minidump_thread_count * sizeof(MINIDUMP_THREAD); + minidump_thread_list_directory.Location.Rva = + static_cast(string_file.SeekGet()); + + // Fields in MINIDUMP_THREAD_LIST. + EXPECT_TRUE( + string_file.Write(&minidump_thread_count, sizeof(minidump_thread_count))); + EXPECT_TRUE(string_file.Write(&minidump_thread, sizeof(minidump_thread))); + + header.StreamDirectoryRva = static_cast(string_file.SeekGet()); + ASSERT_TRUE(string_file.Write(&minidump_thread_list_directory, + sizeof(minidump_thread_list_directory))); + + header.Signature = MINIDUMP_SIGNATURE; + header.Version = MINIDUMP_VERSION; + header.NumberOfStreams = 1; + EXPECT_TRUE(string_file.SeekSet(0)); + EXPECT_TRUE(string_file.Write(&header, sizeof(header))); + + ProcessSnapshotMinidump process_snapshot; + EXPECT_TRUE(process_snapshot.Initialize(&string_file)); + + std::vector threads = process_snapshot.Threads(); + ASSERT_EQ(threads.size(), minidump_thread_count); + + ReadToVector delegate; + threads[0]->Stack()->Read(&delegate); + + EXPECT_EQ(delegate.result, minidump_stack); +} + +TEST(ProcessSnapshotMinidump, CustomMinidumpStreams) { + StringFile string_file; + + MINIDUMP_HEADER header = {}; + ASSERT_TRUE(string_file.Write(&header, sizeof(header))); + + static const char kStreamReservedData[] = "A string"; + static const char kStreamUnreservedData[] = "Another string"; + // In the minidump reserved range + constexpr MinidumpStreamType kStreamTypeReserved1 = + (MinidumpStreamType)0x1111; + // In the crashpad reserved range + constexpr MinidumpStreamType kStreamTypeReserved2 = + (MinidumpStreamType)0x43501111; + constexpr MinidumpStreamType kStreamTypeUnreserved = + (MinidumpStreamType)0xffffffff; + + MINIDUMP_DIRECTORY misc_directory = {}; + RVA reserved1_offset = static_cast(string_file.SeekGet()); + ASSERT_TRUE( + string_file.Write(kStreamReservedData, sizeof(kStreamReservedData))); + RVA reserved2_offset = static_cast(string_file.SeekGet()); + ASSERT_TRUE( + string_file.Write(kStreamReservedData, sizeof(kStreamReservedData))); + RVA unreserved_offset = static_cast(string_file.SeekGet()); + ASSERT_TRUE( + string_file.Write(kStreamUnreservedData, sizeof(kStreamUnreservedData))); + + header.StreamDirectoryRva = static_cast(string_file.SeekGet()); + misc_directory.StreamType = kStreamTypeReserved1; + misc_directory.Location.DataSize = sizeof(kStreamReservedData); + misc_directory.Location.Rva = reserved1_offset; + ASSERT_TRUE(string_file.Write(&misc_directory, sizeof(misc_directory))); + + misc_directory.StreamType = kStreamTypeReserved2; + misc_directory.Location.DataSize = sizeof(kStreamReservedData); + misc_directory.Location.Rva = reserved2_offset; + ASSERT_TRUE(string_file.Write(&misc_directory, sizeof(misc_directory))); + + misc_directory.StreamType = kStreamTypeUnreserved; + misc_directory.Location.DataSize = sizeof(kStreamUnreservedData); + misc_directory.Location.Rva = unreserved_offset; + ASSERT_TRUE(string_file.Write(&misc_directory, sizeof(misc_directory))); + + header.Signature = MINIDUMP_SIGNATURE; + header.Version = MINIDUMP_VERSION; + header.NumberOfStreams = 3; + ASSERT_TRUE(string_file.SeekSet(0)); + ASSERT_TRUE(string_file.Write(&header, sizeof(header))); + + ProcessSnapshotMinidump process_snapshot; + ASSERT_TRUE(process_snapshot.Initialize(&string_file)); + + auto custom_streams = process_snapshot.CustomMinidumpStreams(); + ASSERT_EQ(custom_streams.size(), 1U); + EXPECT_EQ(custom_streams[0]->stream_type(), (uint32_t)kStreamTypeUnreserved); + + auto stream_data = custom_streams[0]->data(); + EXPECT_EQ(stream_data.size(), sizeof(kStreamUnreservedData)); + EXPECT_STREQ((char*)&stream_data.front(), kStreamUnreservedData); +} + +TEST(ProcessSnapshotMinidump, Exception) { + StringFile string_file; + + MINIDUMP_HEADER header = {}; + EXPECT_TRUE(string_file.Write(&header, sizeof(header))); + + uint32_t exception_signo = + static_cast(-1); // crashpad::Signals::kSimulatedSigno + + MINIDUMP_EXCEPTION minidump_exception = {}; + minidump_exception.ExceptionCode = exception_signo; + minidump_exception.ExceptionFlags = 2; + minidump_exception.ExceptionRecord = 4; + minidump_exception.ExceptionAddress = 0xdeedb00f; + minidump_exception.NumberParameters = 2; + minidump_exception.ExceptionInformation[0] = 51; + minidump_exception.ExceptionInformation[1] = 62; + + MINIDUMP_SYSTEM_INFO minidump_system_info = {}; + + minidump_system_info.ProcessorArchitecture = kMinidumpCPUArchitectureARM64; + minidump_system_info.ProductType = kMinidumpOSTypeServer; + minidump_system_info.PlatformId = kMinidumpOSFuchsia; + minidump_system_info.CSDVersionRva = WriteString(&string_file, ""); + + MINIDUMP_DIRECTORY minidump_system_info_directory = {}; + minidump_system_info_directory.StreamType = kMinidumpStreamTypeSystemInfo; + minidump_system_info_directory.Location.DataSize = + sizeof(MINIDUMP_SYSTEM_INFO); + minidump_system_info_directory.Location.Rva = + static_cast(string_file.SeekGet()); + + ASSERT_TRUE( + string_file.Write(&minidump_system_info, sizeof(minidump_system_info))); + + MINIDUMP_EXCEPTION_STREAM minidump_exception_stream = {}; + minidump_exception_stream.ThreadId = 5; + minidump_exception_stream.ExceptionRecord = minidump_exception; + + MinidumpContextARM64 minidump_context = GetArm64MinidumpContext(); + + minidump_exception_stream.ThreadContext.DataSize = sizeof(minidump_context); + minidump_exception_stream.ThreadContext.Rva = + static_cast(string_file.SeekGet()); + + ASSERT_TRUE(string_file.Write(&minidump_context, sizeof(minidump_context))); + + MINIDUMP_DIRECTORY minidump_exception_directory = {}; + minidump_exception_directory.StreamType = kMinidumpStreamTypeException; + minidump_exception_directory.Location.DataSize = + sizeof(MINIDUMP_EXCEPTION_STREAM); + minidump_exception_directory.Location.Rva = + static_cast(string_file.SeekGet()); + + ASSERT_TRUE(string_file.Write(&minidump_exception_stream, + sizeof(minidump_exception_stream))); + + header.StreamDirectoryRva = static_cast(string_file.SeekGet()); + ASSERT_TRUE(string_file.Write(&minidump_exception_directory, + sizeof(minidump_exception_directory))); + ASSERT_TRUE(string_file.Write(&minidump_system_info_directory, + sizeof(minidump_system_info_directory))); + + header.Signature = MINIDUMP_SIGNATURE; + header.Version = MINIDUMP_VERSION; + header.NumberOfStreams = 2; + EXPECT_TRUE(string_file.SeekSet(0)); + EXPECT_TRUE(string_file.Write(&header, sizeof(header))); + + ProcessSnapshotMinidump process_snapshot; + EXPECT_TRUE(process_snapshot.Initialize(&string_file)); + + const ExceptionSnapshot* s = process_snapshot.Exception(); + + EXPECT_EQ(s->ThreadID(), 5UL); + EXPECT_EQ(s->Exception(), exception_signo); + EXPECT_EQ(s->ExceptionInfo(), 2U); + EXPECT_EQ(s->ExceptionAddress(), 0xdeedb00f); + + const std::vector codes = s->Codes(); + EXPECT_EQ(codes.size(), 2UL); + EXPECT_EQ(codes[0], 51UL); + EXPECT_EQ(codes[1], 62UL); + + const CPUContext* ctx_generic = s->Context(); + + ASSERT_EQ(ctx_generic->architecture, CPUArchitecture::kCPUArchitectureARM64); + + const CPUContextARM64* ctx = ctx_generic->arm64; + + EXPECT_EQ(ctx->spsr, 0UL); + + for (unsigned int i = 0; i < 31; i++) { + EXPECT_EQ(ctx->regs[i], i + 1); + } + + EXPECT_EQ(ctx->sp, 32UL); + EXPECT_EQ(ctx->pc, 33UL); + EXPECT_EQ(ctx->fpcr, 98UL); + EXPECT_EQ(ctx->fpsr, 99UL); + + for (unsigned int i = 0; i < 32; i++) { + EXPECT_EQ(ctx->fpsimd[i].lo, i * 2 + 34); + EXPECT_EQ(ctx->fpsimd[i].hi, i * 2 + 35); + } +} + +TEST(ProcessSnapshotMinidump, NoExceptionInMinidump) { + StringFile string_file; + + MINIDUMP_HEADER header = {}; + EXPECT_TRUE(string_file.Write(&header, sizeof(header))); + + header.Signature = MINIDUMP_SIGNATURE; + header.Version = MINIDUMP_VERSION; + header.NumberOfStreams = 0; + EXPECT_TRUE(string_file.SeekSet(0)); + EXPECT_TRUE(string_file.Write(&header, sizeof(header))); + + ProcessSnapshotMinidump process_snapshot; + EXPECT_TRUE(process_snapshot.Initialize(&string_file)); + + const ExceptionSnapshot* s = process_snapshot.Exception(); + EXPECT_EQ(s, nullptr); +} + } // namespace } // namespace test } // namespace crashpad diff --git a/snapshot/minidump/system_snapshot_minidump.cc b/snapshot/minidump/system_snapshot_minidump.cc new file mode 100644 index 00000000..06bae484 --- /dev/null +++ b/snapshot/minidump/system_snapshot_minidump.cc @@ -0,0 +1,198 @@ +// Copyright 2018 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "snapshot/minidump/system_snapshot_minidump.h" + +#include "snapshot/minidump/minidump_string_reader.h" + +namespace crashpad { +namespace internal { + +SystemSnapshotMinidump::SystemSnapshotMinidump() + : SystemSnapshot(), minidump_system_info_(), initialized_() {} + +SystemSnapshotMinidump::~SystemSnapshotMinidump() {} + +bool SystemSnapshotMinidump::Initialize(FileReaderInterface* file_reader, + RVA minidump_system_info_rva, + const std::string& version) { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + + full_version_ = version; + + if (!file_reader->SeekSet(minidump_system_info_rva)) { + return false; + } + + if (!file_reader->ReadExactly(&minidump_system_info_, + sizeof(minidump_system_info_))) { + return false; + } + + if (!ReadMinidumpUTF8String(file_reader, + minidump_system_info_.CSDVersionRva, + &minidump_build_name_)) { + return false; + } + + INITIALIZATION_STATE_SET_VALID(initialized_); + return true; +} + +CPUArchitecture SystemSnapshotMinidump::GetCPUArchitecture() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + switch (minidump_system_info_.ProcessorArchitecture) { + case kMinidumpCPUArchitectureAMD64: + return kCPUArchitectureX86_64; + case kMinidumpCPUArchitectureX86: + case kMinidumpCPUArchitectureX86Win64: + return kCPUArchitectureX86; + case kMinidumpCPUArchitectureARM: + case kMinidumpCPUArchitectureARM32Win64: + return kCPUArchitectureARM; + case kMinidumpCPUArchitectureARM64: + case kMinidumpCPUArchitectureARM64Breakpad: + return kCPUArchitectureARM64; + case kMinidumpCPUArchitectureMIPS: + return kCPUArchitectureMIPSEL; + // No word on how MIPS64 is signalled + + default: + return CPUArchitecture::kCPUArchitectureUnknown; + } +} + +uint32_t SystemSnapshotMinidump::CPURevision() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return minidump_system_info_.ProcessorRevision; +} + +uint8_t SystemSnapshotMinidump::CPUCount() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return minidump_system_info_.NumberOfProcessors; +} + +std::string SystemSnapshotMinidump::CPUVendor() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + if (GetCPUArchitecture() == kCPUArchitectureX86) { + const char* ptr = reinterpret_cast( + minidump_system_info_.Cpu.X86CpuInfo.VendorId); + return std::string(ptr, ptr + (3 * sizeof(uint32_t))); + } else { + return std::string(); + } +} + +void SystemSnapshotMinidump::CPUFrequency(uint64_t* current_hz, + uint64_t* max_hz) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + NOTREACHED(); // https://crashpad.chromium.org/bug/10 +} + +uint32_t SystemSnapshotMinidump::CPUX86Signature() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + NOTREACHED(); // https://crashpad.chromium.org/bug/10 + return 0; +} + +uint64_t SystemSnapshotMinidump::CPUX86Features() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + NOTREACHED(); // https://crashpad.chromium.org/bug/10 + return 0; +} + +uint64_t SystemSnapshotMinidump::CPUX86ExtendedFeatures() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + NOTREACHED(); // https://crashpad.chromium.org/bug/10 + return 0; +} + +uint32_t SystemSnapshotMinidump::CPUX86Leaf7Features() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + NOTREACHED(); // https://crashpad.chromium.org/bug/10 + return 0; +} + +bool SystemSnapshotMinidump::CPUX86SupportsDAZ() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + NOTREACHED(); // https://crashpad.chromium.org/bug/10 + return false; +} + +SystemSnapshot::OperatingSystem SystemSnapshotMinidump::GetOperatingSystem() + const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + switch (minidump_system_info_.PlatformId) { + case kMinidumpOSMacOSX: + return OperatingSystem::kOperatingSystemMacOSX; + case kMinidumpOSWin32s: + case kMinidumpOSWin32Windows: + case kMinidumpOSWin32NT: + return OperatingSystem::kOperatingSystemWindows; + case kMinidumpOSLinux: + return OperatingSystem::kOperatingSystemLinux; + case kMinidumpOSAndroid: + return OperatingSystem::kOperatingSystemAndroid; + case kMinidumpOSFuchsia: + return OperatingSystem::kOperatingSystemFuchsia; + default: + return OperatingSystem::kOperatingSystemUnknown; + } +} + +bool SystemSnapshotMinidump::OSServer() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return minidump_system_info_.ProductType == kMinidumpOSTypeServer; +} + +void SystemSnapshotMinidump::OSVersion(int* major, + int* minor, + int* bugfix, + std::string* build) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + *major = minidump_system_info_.MajorVersion; + *minor = minidump_system_info_.MinorVersion; + *bugfix = minidump_system_info_.BuildNumber; + *build = minidump_build_name_; +} + +std::string SystemSnapshotMinidump::OSVersionFull() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return full_version_; +} + +std::string SystemSnapshotMinidump::MachineDescription() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + NOTREACHED(); // https://crashpad.chromium.org/bug/10 + return std::string(); +} + +bool SystemSnapshotMinidump::NXEnabled() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + NOTREACHED(); // https://crashpad.chromium.org/bug/10 + return false; +} + +void SystemSnapshotMinidump::TimeZone(DaylightSavingTimeStatus* dst_status, + int* standard_offset_seconds, + int* daylight_offset_seconds, + std::string* standard_name, + std::string* daylight_name) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + NOTREACHED(); // https://crashpad.chromium.org/bug/10 +} + +} // namespace internal +} // namespace crashpad diff --git a/snapshot/minidump/system_snapshot_minidump.h b/snapshot/minidump/system_snapshot_minidump.h new file mode 100644 index 00000000..0f2880e1 --- /dev/null +++ b/snapshot/minidump/system_snapshot_minidump.h @@ -0,0 +1,87 @@ +// Copyright 2018 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_SNAPSHOT_MINIDUMP_SYSTEM_SNAPSHOT_MINIDUMP_H_ +#define CRASHPAD_SNAPSHOT_MINIDUMP_SYSTEM_SNAPSHOT_MINIDUMP_H_ + +#include + +#include "base/macros.h" +#include "minidump/minidump_extensions.h" +#include "snapshot/system_snapshot.h" +#include "util/file/file_reader.h" +#include "util/misc/initialization_state_dcheck.h" + +namespace crashpad { +namespace internal { + +//! \brief A SystemSnapshot based on a minidump file. +class SystemSnapshotMinidump : public SystemSnapshot { + public: + SystemSnapshotMinidump(); + ~SystemSnapshotMinidump() override; + + //! \brief Initializes the object. + //! + //! \param[in] file_reader A file reader corresponding to a minidump file. + //! The file reader must support seeking. + //! \param[in] minidump_system_info_rva The file offset in \a file_reader at + //! which the thread’s MINIDUMP_SYSTEM_INFO structure is located. + //! \param[in] version The OS version taken from the build string in + //! MINIDUMP_MISC_INFO_4. + //! + //! \return `true` if the snapshot could be created, `false` otherwise with + //! an appropriate message logged. + bool Initialize(FileReaderInterface* file_reader, + RVA minidump_system_info_rva, + const std::string& version); + + CPUArchitecture GetCPUArchitecture() const override; + uint32_t CPURevision() const override; + uint8_t CPUCount() const override; + std::string CPUVendor() const override; + void CPUFrequency(uint64_t* current_hz, uint64_t* max_hz) const override; + uint32_t CPUX86Signature() const override; + uint64_t CPUX86Features() const override; + uint64_t CPUX86ExtendedFeatures() const override; + uint32_t CPUX86Leaf7Features() const override; + bool CPUX86SupportsDAZ() const override; + OperatingSystem GetOperatingSystem() const override; + bool OSServer() const override; + void OSVersion(int* major, + int* minor, + int* bugfix, + std::string* build) const override; + std::string OSVersionFull() const override; + std::string MachineDescription() const override; + bool NXEnabled() const override; + void TimeZone(DaylightSavingTimeStatus* dst_status, + int* standard_offset_seconds, + int* daylight_offset_seconds, + std::string* standard_name, + std::string* daylight_name) const override; + + private: + MINIDUMP_SYSTEM_INFO minidump_system_info_; + std::string minidump_build_name_; + std::string full_version_; + InitializationStateDcheck initialized_; + + DISALLOW_COPY_AND_ASSIGN(SystemSnapshotMinidump); +}; + +} // namespace internal +} // namespace crashpad + +#endif // CRASHPAD_SNAPSHOT_MINIDUMP_SYSTEM_SNAPSHOT_MINIDUMP_H_ diff --git a/snapshot/minidump/thread_snapshot_minidump.cc b/snapshot/minidump/thread_snapshot_minidump.cc new file mode 100644 index 00000000..8a5a2fd6 --- /dev/null +++ b/snapshot/minidump/thread_snapshot_minidump.cc @@ -0,0 +1,114 @@ +// Copyright 2018 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "snapshot/minidump/thread_snapshot_minidump.h" + +#include +#include + +#include "base/stl_util.h" +#include "minidump/minidump_context.h" + +namespace crashpad { +namespace internal { + +ThreadSnapshotMinidump::ThreadSnapshotMinidump() + : ThreadSnapshot(), + minidump_thread_(), + context_(), + stack_(), + initialized_() {} + +ThreadSnapshotMinidump::~ThreadSnapshotMinidump() {} + +bool ThreadSnapshotMinidump::Initialize(FileReaderInterface* file_reader, + RVA minidump_thread_rva, + CPUArchitecture arch) { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + std::vector minidump_context; + + if (!file_reader->SeekSet(minidump_thread_rva)) { + return false; + } + + if (!file_reader->ReadExactly(&minidump_thread_, sizeof(minidump_thread_))) { + return false; + } + + if (!file_reader->SeekSet(minidump_thread_.ThreadContext.Rva)) { + return false; + } + + minidump_context.resize(minidump_thread_.ThreadContext.DataSize); + + if (!file_reader->ReadExactly(minidump_context.data(), + minidump_context.size())) { + return false; + } + + if (!context_.Initialize(arch, minidump_context)) { + return false; + } + + RVA stack_info_location = + minidump_thread_rva + offsetof(MINIDUMP_THREAD, Stack); + + if (!stack_.Initialize(file_reader, stack_info_location)) { + return false; + } + + INITIALIZATION_STATE_SET_VALID(initialized_); + return true; +} + +uint64_t ThreadSnapshotMinidump::ThreadID() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return minidump_thread_.ThreadId; +} + +int ThreadSnapshotMinidump::SuspendCount() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return minidump_thread_.SuspendCount; +} + +uint64_t ThreadSnapshotMinidump::ThreadSpecificDataAddress() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return minidump_thread_.Teb; +} + +int ThreadSnapshotMinidump::Priority() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return minidump_thread_.Priority; +} + +const CPUContext* ThreadSnapshotMinidump::Context() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return context_.Get(); +} + +const MemorySnapshot* ThreadSnapshotMinidump::Stack() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return &stack_; +} + +std::vector ThreadSnapshotMinidump::ExtraMemory() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + // This doesn't correspond to anything minidump can give us, with the + // exception of the BackingStore field in the MINIDUMP_THREAD_EX structure, + // which is only valid for IA-64. + return std::vector(); +} + +} // namespace internal +} // namespace crashpad diff --git a/snapshot/minidump/thread_snapshot_minidump.h b/snapshot/minidump/thread_snapshot_minidump.h new file mode 100644 index 00000000..262b8da6 --- /dev/null +++ b/snapshot/minidump/thread_snapshot_minidump.h @@ -0,0 +1,80 @@ +// Copyright 2018 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_SNAPSHOT_MINIDUMP_THREAD_SNAPSHOT_MINIDUMP_H_ +#define CRASHPAD_SNAPSHOT_MINIDUMP_THREAD_SNAPSHOT_MINIDUMP_H_ + +#include + +#include "minidump/minidump_extensions.h" +#include "snapshot/cpu_context.h" +#include "snapshot/minidump/memory_snapshot_minidump.h" +#include "snapshot/minidump/minidump_context_converter.h" +#include "snapshot/thread_snapshot.h" +#include "util/file/file_reader.h" +#include "util/misc/initialization_state_dcheck.h" + +namespace crashpad { +namespace internal { + +//! \brief A ThreadSnapshot based on a thread in a minidump file. +class ThreadSnapshotMinidump : public ThreadSnapshot { + public: + ThreadSnapshotMinidump(); + ~ThreadSnapshotMinidump() override; + + //! \brief Initializes the object. + //! + //! \param[in] file_reader A file reader corresponding to a minidump file. + //! The file reader must support seeking. + //! \param[in] minidump_thread_rva The file offset in \a file_reader at which + //! the thread’s MINIDUMP_THREAD structure is located. + //! \param[in] arch The architecture of the system this thread is running on. + //! Used to decode CPU Context. + //! + //! \return `true` if the snapshot could be created, `false` otherwise with + //! an appropriate message logged. + bool Initialize(FileReaderInterface* file_reader, + RVA minidump_thread_rva, + CPUArchitecture arch); + + const CPUContext* Context() const override; + const MemorySnapshot* Stack() const override; + uint64_t ThreadID() const override; + int SuspendCount() const override; + int Priority() const override; + uint64_t ThreadSpecificDataAddress() const override; + std::vector ExtraMemory() const override; + + private: + //! \brief Initializes the CPU Context + //! + //! \param[in] minidump_context the raw bytes of the context data from the + //! minidump file. + //! + //! \return `true` if the context could be decoded without error. + bool InitializeContext(const std::vector& minidump_context); + + MINIDUMP_THREAD minidump_thread_; + MinidumpContextConverter context_; + MemorySnapshotMinidump stack_; + InitializationStateDcheck initialized_; + + DISALLOW_COPY_AND_ASSIGN(ThreadSnapshotMinidump); +}; + +} // namespace internal +} // namespace crashpad + +#endif // CRASHPAD_SNAPSHOT_MINIDUMP_THREAD_SNAPSHOT_MINIDUMP_H_ diff --git a/snapshot/module_snapshot.h b/snapshot/module_snapshot.h index eea74660..f7eb83c8 100644 --- a/snapshot/module_snapshot.h +++ b/snapshot/module_snapshot.h @@ -146,6 +146,7 @@ class ModuleSnapshot { //! Windows with incremental linking. On other platforms \a age will always be //! `0`. //! + //! \sa BuildID() //! \sa DebugFileName() virtual void UUIDAndAge(crashpad::UUID* uuid, uint32_t* age) const = 0; @@ -159,6 +160,20 @@ class ModuleSnapshot { //! \sa UUIDAndAge() virtual std::string DebugFileName() const = 0; + //! \brief Returns the module’s build ID. + //! + //! On ELF platforms, the build ID is a variable-length byte stream that + //! identifies a library uniquely, and is usually used to look up its debug + //! symbols when stored separately. This will return an empty vector if it is + //! unsupported. + //! + //! BuildID() and UUIDAndAge() are never available in the same place. When + //! UUIDAndAge() is unavailable, it will be filled out with the contents of + //! BuildID() (either 0-padded or truncated) and age will be zero. + //! + //! \sa UUIDAndAge() + virtual std::vector BuildID() const = 0; + //! \brief Returns string annotations recorded in the module. //! //! This method retrieves annotations recorded in a module. These annotations diff --git a/snapshot/posix/timezone.cc b/snapshot/posix/timezone.cc index 47c6ecfa..9451e11f 100644 --- a/snapshot/posix/timezone.cc +++ b/snapshot/posix/timezone.cc @@ -18,6 +18,7 @@ #include #include "base/logging.h" +#include "base/stl_util.h" #include "build/build_config.h" namespace crashpad { @@ -60,7 +61,7 @@ void TimeZone(const timeval& snapshot_time, {0, 1, -1, 2, -2, 3, -3, 4, -4, 5, -5, 6, -6, 7, -7, 8, -8, 9, -9, 10, -10, 11, -11, 12, -12}; for (size_t index = 0; - index < arraysize(kMonthDeltas) && !found_transition; + index < base::size(kMonthDeltas) && !found_transition; ++index) { // Look at a day of each month at local noon. Set tm_isdst to -1 to avoid // giving mktime() any hints about whether to consider daylight saving diff --git a/snapshot/posix/timezone_test.cc b/snapshot/posix/timezone_test.cc index 814506fa..b4405bad 100644 --- a/snapshot/posix/timezone_test.cc +++ b/snapshot/posix/timezone_test.cc @@ -22,6 +22,7 @@ #include "base/logging.h" #include "base/macros.h" +#include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "gtest/gtest.h" #include "test/errors.h" @@ -154,7 +155,7 @@ TEST(TimeZone, Basic) { {"UTC", false, 0, 0, "UTC", "UTC"}, }; - for (size_t index = 0; index < arraysize(kTestTimeZones); ++index) { + for (size_t index = 0; index < base::size(kTestTimeZones); ++index) { const auto& test_time_zone = kTestTimeZones[index]; const char* tz = test_time_zone.tz; SCOPED_TRACE(base::StringPrintf("index %zu, tz %s", index, tz)); diff --git a/snapshot/process_snapshot.h b/snapshot/process_snapshot.h index 8d5520cd..08d9f2bc 100644 --- a/snapshot/process_snapshot.h +++ b/snapshot/process_snapshot.h @@ -24,6 +24,7 @@ #include "snapshot/handle_snapshot.h" #include "util/misc/uuid.h" +#include "util/process/process_id.h" namespace crashpad { @@ -31,6 +32,7 @@ class ExceptionSnapshot; class MemoryMapRegionSnapshot; class MemorySnapshot; class ModuleSnapshot; +class ProcessMemory; class SystemSnapshot; class ThreadSnapshot; class UnloadedModuleSnapshot; @@ -49,10 +51,10 @@ class ProcessSnapshot { virtual ~ProcessSnapshot() {} //! \brief Returns the snapshot process’ process ID. - virtual pid_t ProcessID() const = 0; + virtual crashpad::ProcessID ProcessID() const = 0; //! \brief Returns the snapshot process’ parent process’ process ID. - virtual pid_t ParentProcessID() const = 0; + virtual crashpad::ProcessID ParentProcessID() const = 0; //! \brief Returns the time that the snapshot was taken in \a snapshot_time. //! @@ -193,6 +195,14 @@ class ProcessSnapshot { //! are scoped to the lifetime of the ProcessSnapshot object that they //! were obtained from. virtual std::vector ExtraMemory() const = 0; + + //! \brief Returns a ProcessMemory object that allows accessing the process' + //! memory directly. + //! + //! \return A ProcessMemory object. The caller does not take ownership of this + //! object, it is scoped to the lifetime of the ProcessSnapshot object + //! that it was obtained from. + virtual const ProcessMemory* Memory() const = 0; }; } // namespace crashpad diff --git a/snapshot/sanitized/module_snapshot_sanitized.cc b/snapshot/sanitized/module_snapshot_sanitized.cc index 94cb3d00..32b13578 100644 --- a/snapshot/sanitized/module_snapshot_sanitized.cc +++ b/snapshot/sanitized/module_snapshot_sanitized.cc @@ -81,6 +81,10 @@ std::string ModuleSnapshotSanitized::DebugFileName() const { return snapshot_->DebugFileName(); } +std::vector ModuleSnapshotSanitized::BuildID() const { + return snapshot_->BuildID(); +} + std::vector ModuleSnapshotSanitized::AnnotationsVector() const { // TODO(jperaza): If/when AnnotationsVector() begins to be used, determine // whether and how the content should be sanitized. diff --git a/snapshot/sanitized/module_snapshot_sanitized.h b/snapshot/sanitized/module_snapshot_sanitized.h index 4f375ce0..bbe1f5f4 100644 --- a/snapshot/sanitized/module_snapshot_sanitized.h +++ b/snapshot/sanitized/module_snapshot_sanitized.h @@ -56,6 +56,7 @@ class ModuleSnapshotSanitized final : public ModuleSnapshot { ModuleType GetModuleType() const override; void UUIDAndAge(crashpad::UUID* uuid, uint32_t* age) const override; std::string DebugFileName() const override; + std::vector BuildID() const override; std::vector AnnotationsVector() const override; std::map AnnotationsSimpleMap() const override; std::vector AnnotationObjects() const override; diff --git a/snapshot/sanitized/process_snapshot_sanitized.cc b/snapshot/sanitized/process_snapshot_sanitized.cc index 76caeb5a..722abace 100644 --- a/snapshot/sanitized/process_snapshot_sanitized.cc +++ b/snapshot/sanitized/process_snapshot_sanitized.cc @@ -84,12 +84,14 @@ ProcessSnapshotSanitized::~ProcessSnapshotSanitized() = default; bool ProcessSnapshotSanitized::Initialize( const ProcessSnapshot* snapshot, - const std::vector* annotations_whitelist, + std::unique_ptr> annotations_whitelist, + std::unique_ptr>> + memory_range_whitelist, VMAddress target_module_address, bool sanitize_stacks) { INITIALIZATION_STATE_SET_INITIALIZING(initialized_); snapshot_ = snapshot; - annotations_whitelist_ = annotations_whitelist; + annotations_whitelist_ = std::move(annotations_whitelist); sanitize_stacks_ = sanitize_stacks; if (target_module_address) { @@ -140,7 +142,7 @@ bool ProcessSnapshotSanitized::Initialize( if (annotations_whitelist_) { for (const auto module : snapshot_->Modules()) { modules_.emplace_back(std::make_unique( - module, annotations_whitelist_)); + module, annotations_whitelist_.get())); } } @@ -157,16 +159,18 @@ bool ProcessSnapshotSanitized::Initialize( } } + process_memory_.Initialize(snapshot_->Memory(), memory_range_whitelist.get()); + INITIALIZATION_STATE_SET_VALID(initialized_); return true; } -pid_t ProcessSnapshotSanitized::ProcessID() const { +crashpad::ProcessID ProcessSnapshotSanitized::ProcessID() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return snapshot_->ProcessID(); } -pid_t ProcessSnapshotSanitized::ParentProcessID() const { +crashpad::ProcessID ProcessSnapshotSanitized::ParentProcessID() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return snapshot_->ParentProcessID(); } @@ -262,4 +266,9 @@ std::vector ProcessSnapshotSanitized::ExtraMemory() return snapshot_->ExtraMemory(); } +const ProcessMemory* ProcessSnapshotSanitized::Memory() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return &process_memory_; +} + } // namespace crashpad diff --git a/snapshot/sanitized/process_snapshot_sanitized.h b/snapshot/sanitized/process_snapshot_sanitized.h index f5cf5fa6..2d387981 100644 --- a/snapshot/sanitized/process_snapshot_sanitized.h +++ b/snapshot/sanitized/process_snapshot_sanitized.h @@ -29,6 +29,8 @@ #include "util/misc/address_types.h" #include "util/misc/initialization_state_dcheck.h" #include "util/misc/range_set.h" +#include "util/process/process_id.h" +#include "util/process/process_memory_sanitized.h" namespace crashpad { @@ -48,6 +50,8 @@ class ProcessSnapshotSanitized final : public ProcessSnapshot { //! \param[in] annotations_whitelist A list of annotations names to allow to //! be returned by AnnotationsSimpleMap() or from this object's module //! snapshots. If `nullptr`, all annotations will be returned. + //! \param[in] memory_range_whitelist A list of memory ranges to allow to be + //! accessible via Memory(), or `nullptr` to allow all ranges. //! \param[in] target_module_address An address in the target process' //! address space within the bounds of a module to target. If the //! crashing thread's context and stack do not contain any pointers into @@ -59,15 +63,18 @@ class ProcessSnapshotSanitized final : public ProcessSnapshot { //! 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); + bool Initialize( + const ProcessSnapshot* snapshot, + std::unique_ptr> annotations_whitelist, + std::unique_ptr>> + memory_range_whitelist, + VMAddress target_module_address, + bool sanitize_stacks); // ProcessSnapshot: - pid_t ProcessID() const override; - pid_t ParentProcessID() const override; + crashpad::ProcessID ProcessID() const override; + crashpad::ProcessID ParentProcessID() const override; void SnapshotTime(timeval* snapshot_time) const override; void ProcessStartTime(timeval* start_time) const override; void ProcessCPUTimes(timeval* user_time, timeval* system_time) const override; @@ -83,6 +90,7 @@ class ProcessSnapshotSanitized final : public ProcessSnapshot { std::vector MemoryMap() const override; std::vector Handles() const override; std::vector ExtraMemory() const override; + const ProcessMemory* Memory() const override; private: // Only used when annotations_whitelist_ != nullptr. @@ -93,7 +101,8 @@ class ProcessSnapshotSanitized final : public ProcessSnapshot { RangeSet address_ranges_; const ProcessSnapshot* snapshot_; - const std::vector* annotations_whitelist_; + ProcessMemorySanitized process_memory_; + std::unique_ptr> annotations_whitelist_; bool sanitize_stacks_; InitializationStateDcheck initialized_; diff --git a/snapshot/sanitized/process_snapshot_sanitized_test.cc b/snapshot/sanitized/process_snapshot_sanitized_test.cc index cb413e3b..5c5ff1a5 100644 --- a/snapshot/sanitized/process_snapshot_sanitized_test.cc +++ b/snapshot/sanitized/process_snapshot_sanitized_test.cc @@ -145,6 +145,22 @@ void ExpectAnnotations(ProcessSnapshot* snapshot, bool sanitized) { } } +void ExpectProcessMemory(ProcessSnapshot* snapshot, + VMAddress whitelisted_byte, + bool sanitized) { + auto memory = snapshot->Memory(); + + char out; + EXPECT_TRUE(memory->Read(whitelisted_byte, 1, &out)); + + bool unwhitelisted_read = memory->Read(whitelisted_byte + 1, 1, &out); + if (sanitized) { + EXPECT_FALSE(unwhitelisted_read); + } else { + EXPECT_TRUE(unwhitelisted_read); + } +} + class StackSanitizationChecker : public MemorySnapshot::Delegate { public: StackSanitizationChecker() = default; @@ -163,23 +179,9 @@ class StackSanitizationChecker : public MemorySnapshot::Delegate { // MemorySnapshot::Delegate bool MemorySnapshotDelegateRead(void* data, size_t size) override { -#if defined(ADDRESS_SANITIZER) && (defined(OS_LINUX) || defined(OS_ANDROID)) - // AddressSanitizer causes stack variables to be stored separately from the - // call stack. - auto addr_not_in_stack_range = - [](VMAddress addr, VMAddress stack_addr, VMSize stack_size) { - return addr < stack_addr || addr >= stack_addr + stack_size; - }; - EXPECT_PRED3(addr_not_in_stack_range, - addrs_.code_pointer_address, - stack_->Address(), - size); - EXPECT_PRED3(addr_not_in_stack_range, - addrs_.string_address, - stack_->Address(), - size); - return true; -#else + // AddressSanitizer with use-after-return detection causes stack variables + // to be allocated on the heap. +#if !defined(ADDRESS_SANITIZER) size_t pointer_offset; if (!AssignIfInRange(&pointer_offset, addrs_.code_pointer_address - stack_->Address())) { @@ -209,8 +211,8 @@ class StackSanitizationChecker : public MemorySnapshot::Delegate { } else { EXPECT_STREQ(string, kSensitiveStackData); } +#endif // !ADDRESS_SANITIZER return true; -#endif // ADDRESS_SANITIZER && (OS_LINUX || OS_ANDROID) } private: @@ -265,20 +267,34 @@ class SanitizeTest : public MultiprocessExec { ExpectAnnotations(&snapshot, /* sanitized= */ false); ExpectStackData(&snapshot, addrs, /* sanitized= */ false); + ExpectProcessMemory(&snapshot, + addrs.string_address, + /* sanitized= */ false); - std::vector whitelist; - whitelist.push_back(kWhitelistedAnnotationName); + auto annotations_whitelist = std::make_unique>(); + annotations_whitelist->push_back(kWhitelistedAnnotationName); + + auto memory_ranges_whitelist = + std::make_unique>>(); + memory_ranges_whitelist->push_back( + std::make_pair(addrs.string_address, addrs.string_address + 1)); ProcessSnapshotSanitized sanitized; - ASSERT_TRUE(sanitized.Initialize( - &snapshot, &whitelist, addrs.module_address, true)); + ASSERT_TRUE(sanitized.Initialize(&snapshot, + std::move(annotations_whitelist), + std::move(memory_ranges_whitelist), + addrs.module_address, + true)); ExpectAnnotations(&sanitized, /* sanitized= */ true); ExpectStackData(&sanitized, addrs, /* sanitized= */ true); + ExpectProcessMemory(&sanitized, + addrs.string_address, + /* sanitized= */ true); ProcessSnapshotSanitized screened_snapshot; EXPECT_FALSE(screened_snapshot.Initialize( - &snapshot, nullptr, addrs.non_module_address, false)); + &snapshot, nullptr, nullptr, addrs.non_module_address, false)); } DISALLOW_COPY_AND_ASSIGN(SanitizeTest); diff --git a/snapshot/sanitized/sanitization_information.cc b/snapshot/sanitized/sanitization_information.cc index f7be9b08..ff376a39 100644 --- a/snapshot/sanitized/sanitization_information.cc +++ b/snapshot/sanitized/sanitization_information.cc @@ -14,6 +14,8 @@ #include "snapshot/sanitized/sanitization_information.h" +#include + #include "client/annotation.h" namespace crashpad { @@ -21,9 +23,9 @@ namespace crashpad { namespace { template -bool ReadWhitelist(const ProcessMemoryRange& memory, - VMAddress whitelist_address, - std::vector* whitelist) { +bool ReadAnnotationsWhitelist(const ProcessMemoryRange& memory, + VMAddress whitelist_address, + std::vector* whitelist) { if (!whitelist_address) { return true; } @@ -53,9 +55,60 @@ bool ReadWhitelist(const ProcessMemoryRange& memory, bool ReadAnnotationsWhitelist(const ProcessMemoryRange& memory, VMAddress whitelist_address, std::vector* whitelist) { - return memory.Is64Bit() - ? ReadWhitelist(memory, whitelist_address, whitelist) - : ReadWhitelist(memory, whitelist_address, whitelist); + return memory.Is64Bit() ? ReadAnnotationsWhitelist( + memory, whitelist_address, whitelist) + : ReadAnnotationsWhitelist( + memory, whitelist_address, whitelist); +} + +bool ReadMemoryRangeWhitelist( + const ProcessMemoryRange& memory, + VMAddress whitelist_address, + std::vector>* whitelist) { + whitelist->clear(); + if (!whitelist_address) { + return true; + } + + SanitizationMemoryRangeWhitelist list; + if (!memory.Read(whitelist_address, sizeof(list), &list)) { + LOG(ERROR) << "Failed to read memory range whitelist."; + return false; + } + + if (!list.size) { + return true; + } + + // An upper bound of entries that we never expect to see more than. + constexpr size_t kMaxListSize = 256; + if (list.size > kMaxListSize) { + LOG(ERROR) << "Whitelist exceeded maximum, size=" << list.size; + return false; + } + + SanitizationMemoryRangeWhitelist::Range ranges[list.size]; + if (!memory.Read(list.entries, sizeof(ranges), &ranges)) { + LOG(ERROR) << "Failed to read memory range whitelist entries."; + return false; + } + + const VMAddress vm_max = memory.Is64Bit() + ? std::numeric_limits::max() + : std::numeric_limits::max(); + std::vector> local_whitelist; + for (size_t i = 0; i < list.size; i++) { + if (ranges[i].base > vm_max || ranges[i].length > vm_max - ranges[i].base) { + LOG(ERROR) << "Invalid memory range whitelist entry base=" + << ranges[i].base << " length=" << ranges[i].length; + return false; + } + local_whitelist.emplace_back(ranges[i].base, + ranges[i].base + ranges[i].length); + } + + whitelist->swap(local_whitelist); + return true; } } // namespace crashpad diff --git a/snapshot/sanitized/sanitization_information.h b/snapshot/sanitized/sanitization_information.h index 6a6d9089..0717ba7f 100644 --- a/snapshot/sanitized/sanitization_information.h +++ b/snapshot/sanitized/sanitization_information.h @@ -18,6 +18,7 @@ #include #include +#include #include #include "util/misc/address_types.h" @@ -45,10 +46,29 @@ struct SanitizationInformation { //! if there is no target module. VMAddress target_module_address; + //! \brief The address in the client process' address space of a + //! a \a SanitizationMemoryRangeWhitelist, a list of whitelisted address + //! ranges allowed to be accessed by ProcessMemorySanitized. This value + //! is 0 if no memory is allowed to be read using ProcessMemorySanitized. + VMAddress memory_range_whitelist_address; + //! \brief Non-zero if stacks should be sanitized for possible PII. uint8_t sanitize_stacks; }; +//! \brief Describes a list of white listed memory ranges. +struct SanitizationMemoryRangeWhitelist { + //! \brief Describes a range of memory. + struct Range { + VMAddress base; + VMSize length; + }; + + //! \brief Address of an array of |size| elements of type Range. + VMAddress entries; + VMSize size; +}; + #pragma pack(pop) //! \brief Reads an annotations whitelist from another process. @@ -63,6 +83,19 @@ bool ReadAnnotationsWhitelist(const ProcessMemoryRange& memory, VMAddress whitelist_address, std::vector* whitelist); +//! \brief Reads a memory range whitelist from another process. +//! +//! \param[in] memory A memory reader for the target process. +//! \param[in] whitelist_address The address in the target process' address +//! space of a nullptr terminated array of NUL-terminated strings. +//! \param[out] whitelist A list of whitelisted memory regions, valid only if +//! this function returns `true`. +//! \return `true` on success, `false` on failure with a message logged. +bool ReadMemoryRangeWhitelist( + const ProcessMemoryRange& memory, + VMAddress whitelist_address, + std::vector>* whitelist); + } // namespace crashpad #endif // CRASHPAD_SNAPSHOT_SANITIZED_SANITIZATION_INFORMATION_H_ diff --git a/snapshot/sanitized/sanitization_information_test.cc b/snapshot/sanitized/sanitization_information_test.cc index a7bf8265..e426f28b 100644 --- a/snapshot/sanitized/sanitization_information_test.cc +++ b/snapshot/sanitized/sanitization_information_test.cc @@ -14,6 +14,7 @@ #include "snapshot/sanitized/sanitization_information.h" +#include "base/stl_util.h" #include "build/build_config.h" #include "gtest/gtest.h" #include "util/misc/from_pointer_cast.h" @@ -59,8 +60,8 @@ const char* const kNonEmptyWhitelist[] = {"string1", TEST_F(WhitelistTest, NonEmptyWhitelist) { ASSERT_TRUE(ReadWhitelist(kNonEmptyWhitelist)); - ASSERT_EQ(whitelist_.size(), arraysize(kNonEmptyWhitelist) - 1); - for (size_t index = 0; index < arraysize(kNonEmptyWhitelist) - 1; ++index) { + ASSERT_EQ(whitelist_.size(), base::size(kNonEmptyWhitelist) - 1); + for (size_t index = 0; index < base::size(kNonEmptyWhitelist) - 1; ++index) { EXPECT_EQ(whitelist_[index], kNonEmptyWhitelist[index]); } } diff --git a/snapshot/snapshot.gyp b/snapshot/snapshot.gyp index a78d21f9..788ea3d1 100644 --- a/snapshot/snapshot.gyp +++ b/snapshot/snapshot.gyp @@ -109,16 +109,27 @@ 'memory_snapshot_generic.h', 'minidump/minidump_annotation_reader.cc', 'minidump/minidump_annotation_reader.h', + 'minidump/minidump_context_converter.cc', + 'minidump/minidump_context_converter.h', 'minidump/minidump_simple_string_dictionary_reader.cc', 'minidump/minidump_simple_string_dictionary_reader.h', + 'minidump/minidump_stream.h', 'minidump/minidump_string_list_reader.cc', 'minidump/minidump_string_list_reader.h', 'minidump/minidump_string_reader.cc', 'minidump/minidump_string_reader.h', + 'minidump/exception_snapshot_minidump.cc', + 'minidump/exception_snapshot_minidump.h', + 'minidump/memory_snapshot_minidump.cc', + 'minidump/memory_snapshot_minidump.h', 'minidump/module_snapshot_minidump.cc', 'minidump/module_snapshot_minidump.h', 'minidump/process_snapshot_minidump.cc', 'minidump/process_snapshot_minidump.h', + 'minidump/system_snapshot_minidump.cc', + 'minidump/system_snapshot_minidump.h', + 'minidump/thread_snapshot_minidump.cc', + 'minidump/thread_snapshot_minidump.h', 'module_snapshot.h', 'posix/timezone.cc', 'posix/timezone.h', @@ -146,8 +157,6 @@ 'win/capture_memory_delegate_win.h', 'win/memory_map_region_snapshot_win.cc', 'win/memory_map_region_snapshot_win.h', - 'win/memory_snapshot_win.cc', - 'win/memory_snapshot_win.h', 'win/module_snapshot_win.cc', 'win/module_snapshot_win.h', 'win/pe_image_annotations_reader.cc', @@ -203,32 +212,5 @@ }], ], }, - { - 'variables': { - 'conditions': [ - ['OS == "win"', { - 'snapshot_api_target_type%': 'static_library', - }, { - # There are no source files except on Windows. - 'snapshot_api_target_type%': 'none', - }], - ], - }, - 'target_name': 'crashpad_snapshot_api', - 'type': '<(snapshot_api_target_type)', - 'dependencies': [ - 'crashpad_snapshot', - '../compat/compat.gyp:crashpad_compat', - '../third_party/mini_chromium/mini_chromium.gyp:base', - '../util/util.gyp:crashpad_util', - ], - 'include_dirs': [ - '..', - ], - 'sources': [ - 'api/module_annotations_win.cc', - 'api/module_annotations_win.h', - ], - }, ], } diff --git a/snapshot/snapshot_test.gyp b/snapshot/snapshot_test.gyp index b795d92f..cf7b8aec 100644 --- a/snapshot/snapshot_test.gyp +++ b/snapshot/snapshot_test.gyp @@ -57,7 +57,6 @@ 'crashpad_snapshot_test_module_large', 'crashpad_snapshot_test_module_small', 'snapshot.gyp:crashpad_snapshot', - 'snapshot.gyp:crashpad_snapshot_api', '../client/client.gyp:crashpad_client', '../compat/compat.gyp:crashpad_compat', '../test/test.gyp:crashpad_gtest_main', @@ -70,7 +69,6 @@ '..', ], 'sources': [ - 'api/module_annotations_win_test.cc', 'cpu_context_test.cc', 'memory_snapshot_test.cc', 'crashpad_info_client_options_test.cc', @@ -96,7 +94,7 @@ 'win/cpu_context_win_test.cc', 'win/exception_snapshot_win_test.cc', 'win/extra_memory_ranges_test.cc', - 'win/pe_image_annotations_reader_test.cc', + 'win/module_snapshot_win_test.cc', 'win/pe_image_reader_test.cc', 'win/process_reader_win_test.cc', 'win/process_snapshot_win_test.cc', diff --git a/snapshot/system_snapshot.h b/snapshot/system_snapshot.h index a363c0c8..d78cc235 100644 --- a/snapshot/system_snapshot.h +++ b/snapshot/system_snapshot.h @@ -50,6 +50,9 @@ class SystemSnapshot { //! \brief Fuchsia. kOperatingSystemFuchsia, + + //! \brief iOS. + kOperatingSystemIOS, }; //! \brief A system’s daylight saving time status. diff --git a/snapshot/test/test_cpu_context.cc b/snapshot/test/test_cpu_context.cc index 1e785af8..746e3407 100644 --- a/snapshot/test/test_cpu_context.cc +++ b/snapshot/test/test_cpu_context.cc @@ -17,7 +17,7 @@ #include #include -#include "base/macros.h" +#include "base/stl_util.h" namespace crashpad { namespace test { @@ -44,28 +44,28 @@ void InitializeCPUContextFxsave(FxsaveType* fxsave, uint32_t* seed) { 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); + for (size_t st_mm_index = 0; st_mm_index < base::size(fxsave->st_mm); ++st_mm_index) { - for (size_t byte = 0; byte < arraysize(fxsave->st_mm[st_mm_index].st); + for (size_t byte = 0; byte < base::size(fxsave->st_mm[st_mm_index].st); ++byte) { fxsave->st_mm[st_mm_index].st[byte] = static_cast(value++); } for (size_t byte = 0; - byte < arraysize(fxsave->st_mm[st_mm_index].st_reserved); + byte < base::size(fxsave->st_mm[st_mm_index].st_reserved); ++byte) { fxsave->st_mm[st_mm_index].st_reserved[byte] = static_cast(value); } } - for (size_t xmm_index = 0; xmm_index < arraysize(fxsave->xmm); ++xmm_index) { - for (size_t byte = 0; byte < arraysize(fxsave->xmm[xmm_index]); ++byte) { + for (size_t xmm_index = 0; xmm_index < base::size(fxsave->xmm); ++xmm_index) { + for (size_t byte = 0; byte < base::size(fxsave->xmm[xmm_index]); ++byte) { fxsave->xmm[xmm_index][byte] = static_cast(value++); } } - for (size_t byte = 0; byte < arraysize(fxsave->reserved_4); ++byte) { + for (size_t byte = 0; byte < base::size(fxsave->reserved_4); ++byte) { fxsave->reserved_4[byte] = static_cast(value++); } - for (size_t byte = 0; byte < arraysize(fxsave->available); ++byte) { + for (size_t byte = 0; byte < base::size(fxsave->available); ++byte) { fxsave->available[byte] = static_cast(value++); } @@ -174,7 +174,7 @@ void InitializeCPUContextARM(CPUContext* context, uint32_t seed) { uint32_t value = seed; - for (size_t index = 0; index < arraysize(arm->regs); ++index) { + for (size_t index = 0; index < base::size(arm->regs); ++index) { arm->regs[index] = value++; } arm->fp = value++; @@ -185,7 +185,7 @@ void InitializeCPUContextARM(CPUContext* context, uint32_t seed) { arm->pc = value++; arm->cpsr = value++; - for (size_t index = 0; index < arraysize(arm->vfp_regs.vfp); ++index) { + for (size_t index = 0; index < base::size(arm->vfp_regs.vfp); ++index) { arm->vfp_regs.vfp[index] = value++; } arm->vfp_regs.fpscr = value++; @@ -205,14 +205,14 @@ void InitializeCPUContextARM64(CPUContext* context, uint32_t seed) { uint32_t value = seed; - for (size_t index = 0; index < arraysize(arm64->regs); ++index) { + for (size_t index = 0; index < base::size(arm64->regs); ++index) { arm64->regs[index] = value++; } arm64->sp = value++; arm64->pc = value++; - arm64->pstate = value++; + arm64->spsr = value++; - for (size_t index = 0; index < arraysize(arm64->fpsimd); ++index) { + for (size_t index = 0; index < base::size(arm64->fpsimd); ++index) { arm64->fpsimd[index].lo = value++; arm64->fpsimd[index].hi = value++; } @@ -231,7 +231,7 @@ void InitializeCPUContextMIPS(CPUContext* context, uint32_t seed) { uint32_t value = seed; - for (size_t index = 0; index < arraysize(mipsel->regs); ++index) { + for (size_t index = 0; index < base::size(mipsel->regs); ++index) { mipsel->regs[index] = value++; } @@ -242,7 +242,7 @@ void InitializeCPUContextMIPS(CPUContext* context, uint32_t seed) { mipsel->cp0_status = value++; mipsel->cp0_cause = value++; - for (size_t index = 0; index < arraysize(mipsel->fpregs.fregs); ++index) { + for (size_t index = 0; index < base::size(mipsel->fpregs.fregs); ++index) { mipsel->fpregs.fregs[index]._fp_fregs = static_cast(value++); } @@ -267,7 +267,7 @@ void InitializeCPUContextMIPS64(CPUContext* context, uint32_t seed) { uint64_t value = seed; - for (size_t index = 0; index < arraysize(mips64->regs); ++index) { + for (size_t index = 0; index < base::size(mips64->regs); ++index) { mips64->regs[index] = value++; } @@ -278,7 +278,7 @@ void InitializeCPUContextMIPS64(CPUContext* context, uint32_t seed) { mips64->cp0_status = value++; mips64->cp0_cause = value++; - for (size_t index = 0; index < arraysize(mips64->fpregs.dregs); ++index) { + for (size_t index = 0; index < base::size(mips64->fpregs.dregs); ++index) { mips64->fpregs.dregs[index] = static_cast(value++); } diff --git a/snapshot/test/test_module_snapshot.cc b/snapshot/test/test_module_snapshot.cc index 9141edad..ad0c2dec 100644 --- a/snapshot/test/test_module_snapshot.cc +++ b/snapshot/test/test_module_snapshot.cc @@ -30,11 +30,9 @@ TestModuleSnapshot::TestModuleSnapshot() debug_file_name_(), annotations_vector_(), annotations_simple_map_(), - extra_memory_ranges_() { -} + extra_memory_ranges_() {} -TestModuleSnapshot::~TestModuleSnapshot() { -} +TestModuleSnapshot::~TestModuleSnapshot() {} std::string TestModuleSnapshot::Name() const { return name_; @@ -85,6 +83,10 @@ std::string TestModuleSnapshot::DebugFileName() const { return debug_file_name_; } +std::vector TestModuleSnapshot::BuildID() const { + return build_id_; +} + std::vector TestModuleSnapshot::AnnotationsVector() const { return annotations_vector_; } diff --git a/snapshot/test/test_module_snapshot.h b/snapshot/test/test_module_snapshot.h index d1262fa6..fb84aaf4 100644 --- a/snapshot/test/test_module_snapshot.h +++ b/snapshot/test/test_module_snapshot.h @@ -64,6 +64,9 @@ class TestModuleSnapshot final : public ModuleSnapshot { uuid_ = uuid; age_ = age; } + void SetBuildID(const std::vector& build_id) { + build_id_ = build_id; + } void SetDebugFileName(const std::string& debug_file_name) { debug_file_name_ = debug_file_name; } @@ -101,6 +104,7 @@ class TestModuleSnapshot final : public ModuleSnapshot { ModuleType GetModuleType() const override; void UUIDAndAge(crashpad::UUID* uuid, uint32_t* age) const override; std::string DebugFileName() const override; + std::vector BuildID() const override; std::vector AnnotationsVector() const override; std::map AnnotationsSimpleMap() const override; std::vector AnnotationObjects() const override; @@ -117,6 +121,7 @@ class TestModuleSnapshot final : public ModuleSnapshot { ModuleType module_type_; uint32_t age_; crashpad::UUID uuid_; + std::vector build_id_; std::string debug_file_name_; std::vector annotations_vector_; std::map annotations_simple_map_; diff --git a/snapshot/test/test_process_snapshot.cc b/snapshot/test/test_process_snapshot.cc index c430fd99..42a049a5 100644 --- a/snapshot/test/test_process_snapshot.cc +++ b/snapshot/test/test_process_snapshot.cc @@ -35,17 +35,18 @@ TestProcessSnapshot::TestProcessSnapshot() exception_(), memory_map_(), handles_(), - extra_memory_() { + extra_memory_(), + process_memory_() { } TestProcessSnapshot::~TestProcessSnapshot() { } -pid_t TestProcessSnapshot::ProcessID() const { +crashpad::ProcessID TestProcessSnapshot::ProcessID() const { return process_id_; } -pid_t TestProcessSnapshot::ParentProcessID() const { +crashpad::ProcessID TestProcessSnapshot::ParentProcessID() const { return parent_process_id_; } @@ -126,5 +127,9 @@ std::vector TestProcessSnapshot::ExtraMemory() const { return extra_memory; } +const ProcessMemory* TestProcessSnapshot::Memory() const { + return process_memory_.get(); +} + } // namespace test } // namespace crashpad diff --git a/snapshot/test/test_process_snapshot.h b/snapshot/test/test_process_snapshot.h index 5c63e121..5495cba1 100644 --- a/snapshot/test/test_process_snapshot.h +++ b/snapshot/test/test_process_snapshot.h @@ -35,6 +35,8 @@ #include "snapshot/thread_snapshot.h" #include "snapshot/unloaded_module_snapshot.h" #include "util/misc/uuid.h" +#include "util/process/process_id.h" +#include "util/process/process_memory.h" namespace crashpad { namespace test { @@ -46,8 +48,10 @@ class TestProcessSnapshot final : public ProcessSnapshot { TestProcessSnapshot(); ~TestProcessSnapshot() override; - void SetProcessID(pid_t process_id) { process_id_ = process_id; } - void SetParentProcessID(pid_t parent_process_id) { + void SetProcessID(crashpad::ProcessID process_id) { + process_id_ = process_id; + } + void SetParentProcessID(crashpad::ProcessID parent_process_id) { parent_process_id_ = parent_process_id; } void SetSnapshotTime(const timeval& snapshot_time) { @@ -134,10 +138,19 @@ class TestProcessSnapshot final : public ProcessSnapshot { extra_memory_.push_back(std::move(extra_memory)); } + //! \brief Add a process memory object to be returned by Memory(). + //! + //! \param[in] process_memory The memory object that will be returned by + //! Memory(). The TestProcessSnapshot object takes ownership of \a + //! extra_memory. + void SetProcessMemory(std::unique_ptr process_memory) { + process_memory_ = std::move(process_memory); + } + // ProcessSnapshot: - pid_t ProcessID() const override; - pid_t ParentProcessID() const override; + crashpad::ProcessID ProcessID() const override; + crashpad::ProcessID ParentProcessID() const override; void SnapshotTime(timeval* snapshot_time) const override; void ProcessStartTime(timeval* start_time) const override; void ProcessCPUTimes(timeval* user_time, timeval* system_time) const override; @@ -153,10 +166,11 @@ class TestProcessSnapshot final : public ProcessSnapshot { std::vector MemoryMap() const override; std::vector Handles() const override; std::vector ExtraMemory() const override; + const ProcessMemory* Memory() const override; private: - pid_t process_id_; - pid_t parent_process_id_; + crashpad::ProcessID process_id_; + crashpad::ProcessID parent_process_id_; timeval snapshot_time_; timeval process_start_time_; timeval process_cpu_user_time_; @@ -172,6 +186,7 @@ class TestProcessSnapshot final : public ProcessSnapshot { std::vector> memory_map_; std::vector handles_; std::vector> extra_memory_; + std::unique_ptr process_memory_; DISALLOW_COPY_AND_ASSIGN(TestProcessSnapshot); }; diff --git a/snapshot/win/capture_memory_delegate_win.cc b/snapshot/win/capture_memory_delegate_win.cc index fc1c608b..ee5e5d64 100644 --- a/snapshot/win/capture_memory_delegate_win.cc +++ b/snapshot/win/capture_memory_delegate_win.cc @@ -17,7 +17,7 @@ #include #include "base/numerics/safe_conversions.h" -#include "snapshot/win/memory_snapshot_win.h" +#include "snapshot/memory_snapshot_generic.h" namespace crashpad { namespace internal { @@ -25,7 +25,7 @@ namespace internal { CaptureMemoryDelegateWin::CaptureMemoryDelegateWin( ProcessReaderWin* process_reader, const ProcessReaderWin::Thread& thread, - std::vector>* snapshots, + std::vector>* snapshots, uint32_t* budget_remaining) : stack_(thread.stack_region_address, thread.stack_region_size), process_reader_(process_reader), @@ -39,7 +39,8 @@ bool CaptureMemoryDelegateWin::Is64Bit() const { bool CaptureMemoryDelegateWin::ReadMemory(uint64_t at, uint64_t num_bytes, void* into) const { - return process_reader_->ReadMemory(at, num_bytes, into); + return process_reader_->Memory()->Read( + at, base::checked_cast(num_bytes), into); } std::vector> CaptureMemoryDelegateWin::GetReadableRanges( @@ -56,9 +57,9 @@ void CaptureMemoryDelegateWin::AddNewMemorySnapshot( return; if (budget_remaining_ && *budget_remaining_ == 0) return; - snapshots_->push_back(std::make_unique()); - internal::MemorySnapshotWin* snapshot = snapshots_->back().get(); - snapshot->Initialize(process_reader_, range.base(), range.size()); + snapshots_->push_back(std::make_unique()); + internal::MemorySnapshotGeneric* snapshot = snapshots_->back().get(); + snapshot->Initialize(process_reader_->Memory(), range.base(), range.size()); if (budget_remaining_) { if (!base::IsValueInRangeForNumericType(range.size())) { *budget_remaining_ = 0; diff --git a/snapshot/win/capture_memory_delegate_win.h b/snapshot/win/capture_memory_delegate_win.h index 175b4c95..85c3db97 100644 --- a/snapshot/win/capture_memory_delegate_win.h +++ b/snapshot/win/capture_memory_delegate_win.h @@ -28,7 +28,7 @@ namespace crashpad { namespace internal { -class MemorySnapshotWin; +class MemorySnapshotGeneric; class CaptureMemoryDelegateWin : public CaptureMemory::Delegate { public: @@ -38,15 +38,15 @@ class CaptureMemoryDelegateWin : public CaptureMemory::Delegate { //! \param[in] thread The thread being inspected. Memory ranges overlapping //! this thread's stack will be ignored on the assumption that they're //! already captured elsewhere. - //! \param[in] snapshots A vector of MemorySnapshotWin to which the captured - //! memory will be added. + //! \param[in] snapshots A vector of MemorySnapshotGeneric to which the + //! captured memory will be added. //! \param[in] budget_remaining If non-null, a pointer to the remaining number //! of bytes to capture. If this is `0`, no further memory will be //! captured. CaptureMemoryDelegateWin( ProcessReaderWin* process_reader, const ProcessReaderWin::Thread& thread, - std::vector>* snapshots, + std::vector>* snapshots, uint32_t* budget_remaining); // MemoryCaptureDelegate: @@ -60,7 +60,7 @@ class CaptureMemoryDelegateWin : public CaptureMemory::Delegate { private: CheckedRange stack_; ProcessReaderWin* process_reader_; // weak - std::vector>* snapshots_; // weak + std::vector>* snapshots_; // weak uint32_t* budget_remaining_; }; diff --git a/snapshot/win/cpu_context_win.cc b/snapshot/win/cpu_context_win.cc index 1a76c6d9..db2e8036 100644 --- a/snapshot/win/cpu_context_win.cc +++ b/snapshot/win/cpu_context_win.cc @@ -126,7 +126,13 @@ void CommonInitializeX86Context(const T& context, CPUContextX86* out) { } // namespace -#if defined(ARCH_CPU_64_BITS) +#if defined(ARCH_CPU_X86) + +void InitializeX86Context(const CONTEXT& context, CPUContextX86* out) { + CommonInitializeX86Context(context, out); +} + +#elif defined(ARCH_CPU_X86_64) void InitializeX86Context(const WOW64_CONTEXT& context, CPUContextX86* out) { CommonInitializeX86Context(context, out); @@ -192,12 +198,36 @@ void InitializeX64Context(const CONTEXT& context, CPUContextX86_64* out) { } } -#else // ARCH_CPU_64_BITS +#elif defined(ARCH_CPU_ARM64) -void InitializeX86Context(const CONTEXT& context, CPUContextX86* out) { - CommonInitializeX86Context(context, out); +void InitializeARM64Context(const CONTEXT& context, CPUContextARM64* out) { + memset(out, 0, sizeof(*out)); + + LOG_IF(ERROR, !HasContextPart(context, CONTEXT_ARM64)) << "non-arm64 context"; + + if (HasContextPart(context, CONTEXT_CONTROL)) { + out->spsr = context.Cpsr; + out->pc = context.Pc; + out->regs[30] = context.Lr; + out->sp = context.Sp; + out->regs[29] = context.Fp; + } + + if (HasContextPart(context, CONTEXT_INTEGER)) { + memcpy(&out->regs[0], &context.X0, 18 * sizeof(context.X0)); + // Don't copy x18 which is reserved as platform register. + memcpy(&out->regs[19], &context.X19, 10 * sizeof(context.X0)); + } + + if (HasContextPart(context, CONTEXT_FLOATING_POINT)) { + static_assert(sizeof(out->fpsimd) == sizeof(context.V), + "types must be equivalent"); + memcpy(&out->fpsimd, &context.V, sizeof(out->fpsimd)); + } } -#endif // ARCH_CPU_64_BITS +#else +#error Unsupported Windows Arch +#endif // ARCH_CPU_X86 } // namespace crashpad diff --git a/snapshot/win/cpu_context_win.h b/snapshot/win/cpu_context_win.h index a384ccb3..9718f49c 100644 --- a/snapshot/win/cpu_context_win.h +++ b/snapshot/win/cpu_context_win.h @@ -23,8 +23,17 @@ namespace crashpad { struct CPUContextX86; struct CPUContextX86_64; +struct CPUContextARM64; -#if defined(ARCH_CPU_64_BITS) || DOXYGEN +#if defined(ARCH_CPU_X86) || DOXYGEN + +//! \brief Initializes a CPUContextX86 structure from a native context structure +//! on Windows. +void InitializeX86Context(const CONTEXT& context, CPUContextX86* out); + +#endif // ARCH_CPU_X86 + +#if defined(ARCH_CPU_X86_64) || DOXYGEN //! \brief Initializes a CPUContextX86 structure from a native context structure //! on Windows. @@ -34,13 +43,15 @@ void InitializeX86Context(const WOW64_CONTEXT& context, CPUContextX86* out); //! structure on Windows. void InitializeX64Context(const CONTEXT& context, CPUContextX86_64* out); -#else // ARCH_CPU_64_BITS +#endif // ARCH_CPU_X86_64 -//! \brief Initializes a CPUContextX86 structure from a native context structure -//! on Windows. -void InitializeX86Context(const CONTEXT& context, CPUContextX86* out); +#if defined(ARCH_CPU_ARM64) || DOXYGEN -#endif // ARCH_CPU_64_BITS +//! \brief Initializes a CPUContextARM64 structure from a native context +//! structure on Windows. +void InitializeARM64Context(const CONTEXT& context, CPUContextARM64* out); + +#endif // ARCH_CPU_ARM64 } // namespace crashpad diff --git a/snapshot/win/cpu_context_win_test.cc b/snapshot/win/cpu_context_win_test.cc index aa2afe9b..e63f04e5 100644 --- a/snapshot/win/cpu_context_win_test.cc +++ b/snapshot/win/cpu_context_win_test.cc @@ -16,11 +16,11 @@ #include -#include "base/macros.h" +#include "base/stl_util.h" #include "build/build_config.h" #include "gtest/gtest.h" -#include "test/hex_string.h" #include "snapshot/cpu_context.h" +#include "test/hex_string.h" namespace crashpad { namespace test { @@ -87,13 +87,13 @@ void TestInitializeX86Context_FsaveWithoutFxsave() { for (size_t st_mm = 0; st_mm < 7; ++st_mm) { EXPECT_EQ( BytesToHexString(cpu_context_x86.fxsave.st_mm[st_mm].st, - arraysize(cpu_context_x86.fxsave.st_mm[st_mm].st)), - std::string(arraysize(cpu_context_x86.fxsave.st_mm[st_mm].st) * 2, + base::size(cpu_context_x86.fxsave.st_mm[st_mm].st)), + std::string(base::size(cpu_context_x86.fxsave.st_mm[st_mm].st) * 2, '0')) << "st_mm " << st_mm; } EXPECT_EQ(BytesToHexString(cpu_context_x86.fxsave.st_mm[7].st, - arraysize(cpu_context_x86.fxsave.st_mm[7].st)), + base::size(cpu_context_x86.fxsave.st_mm[7].st)), "0000000000000080ff7f"); EXPECT_EQ(cpu_context_x86.dr0, 3u); diff --git a/snapshot/win/crashpad_snapshot_test_crashing_child.cc b/snapshot/win/crashpad_snapshot_test_crashing_child.cc index 759cc138..cc1369e6 100644 --- a/snapshot/win/crashpad_snapshot_test_crashing_child.cc +++ b/snapshot/win/crashpad_snapshot_test_crashing_child.cc @@ -16,10 +16,10 @@ #include #include "base/logging.h" -#include "build/build_config.h" #include "client/crashpad_client.h" #include "util/misc/capture_context.h" #include "util/win/address_types.h" +#include "util/win/context_wrappers.h" int wmain(int argc, wchar_t* argv[]) { CHECK_EQ(argc, 2); @@ -32,11 +32,9 @@ int wmain(int argc, wchar_t* argv[]) { CONTEXT context; crashpad::CaptureContext(&context); -#if defined(ARCH_CPU_64_BITS) - crashpad::WinVMAddress break_address = context.Rip; -#else - crashpad::WinVMAddress break_address = context.Eip; -#endif + crashpad::WinVMAddress break_address = + reinterpret_cast( + crashpad::ProgramCounterFromCONTEXT(&context)); // This does not used CheckedWriteFile() because at high optimization // settings, a lot of logging code can be inlined, causing there to be a large diff --git a/snapshot/win/crashpad_snapshot_test_dump_without_crashing.cc b/snapshot/win/crashpad_snapshot_test_dump_without_crashing.cc index e2c524ae..cc441a40 100644 --- a/snapshot/win/crashpad_snapshot_test_dump_without_crashing.cc +++ b/snapshot/win/crashpad_snapshot_test_dump_without_crashing.cc @@ -20,6 +20,7 @@ #include "client/simulate_crash.h" #include "util/misc/capture_context.h" #include "util/win/address_types.h" +#include "util/win/context_wrappers.h" int wmain(int argc, wchar_t* argv[]) { CHECK_EQ(argc, 2); @@ -32,11 +33,9 @@ int wmain(int argc, wchar_t* argv[]) { CONTEXT context; crashpad::CaptureContext(&context); -#if defined(ARCH_CPU_64_BITS) - crashpad::WinVMAddress break_address = context.Rip; -#else - crashpad::WinVMAddress break_address = context.Eip; -#endif + crashpad::WinVMAddress break_address = + reinterpret_cast( + crashpad::ProgramCounterFromCONTEXT(&context)); // This does not used CheckedWriteFile() because at high optimization // settings, a lot of logging code can be inlined, causing there to be a large diff --git a/snapshot/win/crashpad_snapshot_test_image_reader.cc b/snapshot/win/crashpad_snapshot_test_image_reader.cc index 4ebd98e9..8bccbd62 100644 --- a/snapshot/win/crashpad_snapshot_test_image_reader.cc +++ b/snapshot/win/crashpad_snapshot_test_image_reader.cc @@ -15,6 +15,7 @@ #include #include "base/logging.h" +#include "base/stl_util.h" #include "client/crashpad_info.h" #include "util/file/file_io.h" #include "util/synchronization/semaphore.h" @@ -28,7 +29,7 @@ DWORD WINAPI LotsOfReferencesThreadProc(void* param) { // Allocate a bunch of pointers to things on the stack. int* pointers[1000]; - for (size_t i = 0; i < arraysize(pointers); ++i) { + for (size_t i = 0; i < base::size(pointers); ++i) { pointers[i] = new int[2048]; } @@ -52,7 +53,7 @@ int wmain(int argc, wchar_t* argv[]) { // verify the cap on pointed-to memory. crashpad::Semaphore semaphore(0); crashpad::ScopedKernelHANDLE threads[100]; - for (size_t i = 0; i < arraysize(threads); ++i) { + for (size_t i = 0; i < base::size(threads); ++i) { threads[i].reset(CreateThread(nullptr, 0, &LotsOfReferencesThreadProc, @@ -65,7 +66,7 @@ int wmain(int argc, wchar_t* argv[]) { } } - for (size_t i = 0; i < arraysize(threads); ++i) { + for (size_t i = 0; i < base::size(threads); ++i) { semaphore.Wait(); } diff --git a/snapshot/win/end_to_end_test.py b/snapshot/win/end_to_end_test.py index fd602fbc..b1919ade 100755 --- a/snapshot/win/end_to_end_test.py +++ b/snapshot/win/end_to_end_test.py @@ -29,479 +29,452 @@ import win32con import win32pipe import winerror - g_temp_dirs = [] g_had_failures = False def MakeTempDir(): - global g_temp_dirs - new_dir = tempfile.mkdtemp() - g_temp_dirs.append(new_dir) - return new_dir + global g_temp_dirs + new_dir = tempfile.mkdtemp() + g_temp_dirs.append(new_dir) + return new_dir def CleanUpTempDirs(): - global g_temp_dirs - for d in g_temp_dirs: - subprocess.call(['rmdir', '/s', '/q', d], shell=True) + global g_temp_dirs + for d in g_temp_dirs: + subprocess.call(['rmdir', '/s', '/q', d], shell=True) def FindInstalledWindowsApplication(app_path): - search_paths = [os.getenv('PROGRAMFILES(X86)'), - os.getenv('PROGRAMFILES'), - os.getenv('PROGRAMW6432'), - os.getenv('LOCALAPPDATA')] - search_paths += os.getenv('PATH', '').split(os.pathsep) + search_paths = [ + os.getenv('PROGRAMFILES(X86)'), + os.getenv('PROGRAMFILES'), + os.getenv('PROGRAMW6432'), + os.getenv('LOCALAPPDATA') + ] + search_paths += os.getenv('PATH', '').split(os.pathsep) - for search_path in search_paths: - if not search_path: - continue - path = os.path.join(search_path, app_path) - if os.path.isfile(path): - return path + for search_path in search_paths: + if not search_path: + continue + path = os.path.join(search_path, app_path) + if os.path.isfile(path): + return path - return None - - -def GetCdbPath(): - """Search in some reasonable places to find cdb.exe. Searches x64 before x86 - and newer versions before older versions. - """ - possible_paths = ( - os.path.join('Windows Kits', '10', 'Debuggers', 'x64'), - os.path.join('Windows Kits', '10', 'Debuggers', 'x86'), - os.path.join('Windows Kits', '8.1', 'Debuggers', 'x64'), - os.path.join('Windows Kits', '8.1', 'Debuggers', 'x86'), - os.path.join('Windows Kits', '8.0', 'Debuggers', 'x64'), - os.path.join('Windows Kits', '8.0', 'Debuggers', 'x86'), - 'Debugging Tools For Windows (x64)', - 'Debugging Tools For Windows (x86)', - 'Debugging Tools For Windows',) - for possible_path in possible_paths: - app_path = os.path.join(possible_path, 'cdb.exe') - app_path = FindInstalledWindowsApplication(app_path) - if app_path: - return app_path - return None - - -def NamedPipeExistsAndReady(pipe_name): - """Returns False if pipe_name does not exist. If pipe_name does exist, blocks - until the pipe is ready to service clients, and then returns True. - - This is used as a drop-in replacement for os.path.exists() and os.access() to - test for the pipe's existence. Both of those calls tickle the pipe in a way - that appears to the server to be a client connecting, triggering error - messages when no data is received. - - Although this function only needs to test pipe existence (waiting for - CreateNamedPipe()), it actually winds up testing pipe readiness - (waiting for ConnectNamedPipe()). This is unnecessary but harmless. - """ - try: - win32pipe.WaitNamedPipe(pipe_name, win32pipe.NMPWAIT_WAIT_FOREVER) - except pywintypes.error as e: - if e[0] == winerror.ERROR_FILE_NOT_FOUND: - return False - raise - return True - - -def GetDumpFromProgram( - out_dir, pipe_name, executable_name, expect_exit_code, *args): - """Initialize a crash database, and run |executable_name| connecting to a - crash handler. If pipe_name is set, crashpad_handler will be started first. If - pipe_name is empty, the executable is responsible for starting - crashpad_handler. *args will be passed after other arguments to - executable_name. If the child process does not exit with |expect_exit_code|, - an exception will be raised. Returns the path to the minidump generated by - crashpad_handler for further testing. - """ - test_database = MakeTempDir() - handler = None - - try: - subprocess.check_call( - [os.path.join(out_dir, 'crashpad_database_util.exe'), '--create', - '--database=' + test_database]) - - if pipe_name is not None: - handler = subprocess.Popen([ - os.path.join(out_dir, 'crashpad_handler.com'), - '--pipe-name=' + pipe_name, - '--database=' + test_database - ]) - - # Wait until the server is ready. - printed = False - while not NamedPipeExistsAndReady(pipe_name): - if not printed: - print('Waiting for crashpad_handler to be ready...') - printed = True - time.sleep(0.001) - - command = [os.path.join(out_dir, executable_name), pipe_name] + list(args) - else: - command = ([os.path.join(out_dir, executable_name), - os.path.join(out_dir, 'crashpad_handler.com'), - test_database] + - list(args)) - print('Running %s' % os.path.basename(command[0])) - exit_code = subprocess.call(command) - if exit_code != expect_exit_code: - raise subprocess.CalledProcessError(exit_code, executable_name) - - out = subprocess.check_output([ - os.path.join(out_dir, 'crashpad_database_util.exe'), - '--database=' + test_database, - '--show-pending-reports', - '--show-all-report-info', - ]) - for line in out.splitlines(): - if line.strip().startswith('Path:'): - return line.partition(':')[2].strip() - finally: - if handler: - handler.kill() - - -def GetDumpFromCrashyProgram(out_dir, pipe_name): - return GetDumpFromProgram(out_dir, - pipe_name, - 'crashy_program.exe', - win32con.EXCEPTION_ACCESS_VIOLATION) - - -def GetDumpFromOtherProgram(out_dir, pipe_name, *args): - return GetDumpFromProgram( - out_dir, pipe_name, 'crash_other_program.exe', 0, *args) - - -def GetDumpFromSignal(out_dir, pipe_name, *args): - STATUS_FATAL_APP_EXIT = 0x40000015 # Not known by win32con. - return GetDumpFromProgram(out_dir, - pipe_name, - 'crashy_signal.exe', - STATUS_FATAL_APP_EXIT, - *args) - - -def GetDumpFromSelfDestroyingProgram(out_dir, pipe_name): - return GetDumpFromProgram(out_dir, - pipe_name, - 'self_destroying_program.exe', - win32con.EXCEPTION_BREAKPOINT) - - -def GetDumpFromZ7Program(out_dir, pipe_name): - return GetDumpFromProgram(out_dir, - pipe_name, - 'crashy_z7_loader.exe', - win32con.EXCEPTION_ACCESS_VIOLATION) - - -class CdbRun(object): - """Run cdb.exe passing it a cdb command and capturing the output. - `Check()` searches for regex patterns in sequence allowing verification of - expected output. - """ - - def __init__(self, cdb_path, dump_path, command): - # Run a command line that loads the dump, runs the specified cdb command, - # and then quits, and capturing stdout. - self.out = subprocess.check_output([ - cdb_path, - '-z', dump_path, - '-c', command + ';q' - ]) - - def Check(self, pattern, message, re_flags=0): - match_obj = re.search(pattern, self.out, re_flags) - if match_obj: - # Matched. Consume up to end of match. - self.out = self.out[match_obj.end(0):] - print('ok - %s' % message) - sys.stdout.flush() - else: - print('-' * 80, file=sys.stderr) - print('FAILED - %s' % message, file=sys.stderr) - print('-' * 80, file=sys.stderr) - print('did not match:\n %s' % pattern, file=sys.stderr) - print('-' * 80, file=sys.stderr) - print('remaining output was:\n %s' % self.out, file=sys.stderr) - print('-' * 80, file=sys.stderr) - sys.stderr.flush() - global g_had_failures - g_had_failures = True - - def Find(self, pattern, re_flags=0): - match_obj = re.search(pattern, self.out, re_flags) - if match_obj: - # Matched. Consume up to end of match. - self.out = self.out[match_obj.end(0):] - return match_obj return None -def RunTests(cdb_path, - dump_path, - start_handler_dump_path, - destroyed_dump_path, - z7_dump_path, - other_program_path, - other_program_no_exception_path, - sigabrt_main_path, - sigabrt_background_path, - pipe_name): - """Runs various tests in sequence. Runs a new cdb instance on the dump for - each block of tests to reduce the chances that output from one command is - confused for output from another. - """ - out = CdbRun(cdb_path, dump_path, '.ecxr') - out.Check('This dump file has an exception of interest stored in it', - 'captured exception') +def GetCdbPath(): + """Search in some reasonable places to find cdb.exe. Searches x64 before x86 + and newer versions before older versions. + """ + possible_paths = ( + os.path.join('Windows Kits', '10', 'Debuggers', 'x64'), + os.path.join('Windows Kits', '10', 'Debuggers', 'x86'), + os.path.join('Windows Kits', '8.1', 'Debuggers', 'x64'), + os.path.join('Windows Kits', '8.1', 'Debuggers', 'x86'), + os.path.join('Windows Kits', '8.0', 'Debuggers', 'x64'), + os.path.join('Windows Kits', '8.0', 'Debuggers', 'x86'), + 'Debugging Tools For Windows (x64)', + 'Debugging Tools For Windows (x86)', + 'Debugging Tools For Windows', + ) + for possible_path in possible_paths: + app_path = os.path.join(possible_path, 'cdb.exe') + app_path = FindInstalledWindowsApplication(app_path) + if app_path: + return app_path + return None - # When SomeCrashyFunction is inlined, cdb doesn't demangle its namespace as - # "`anonymous namespace'" and instead gives the decorated form. - out.Check('crashy_program!crashpad::(`anonymous namespace\'|\?A0x[0-9a-f]+)::' - 'SomeCrashyFunction', - 'exception at correct location') - out = CdbRun(cdb_path, start_handler_dump_path, '.ecxr') - out.Check('This dump file has an exception of interest stored in it', - 'captured exception (using StartHandler())') - out.Check('crashy_program!crashpad::(`anonymous namespace\'|\?A0x[0-9a-f]+)::' - 'SomeCrashyFunction', - 'exception at correct location (using StartHandler())') +def NamedPipeExistsAndReady(pipe_name): + """Returns False if pipe_name does not exist. If pipe_name does exist, + blocks until the pipe is ready to service clients, and then returns True. - out = CdbRun(cdb_path, dump_path, '!peb') - out.Check(r'PEB at', 'found the PEB') - out.Check(r'Ldr\.InMemoryOrderModuleList:.*\d+ \. \d+', 'PEB_LDR_DATA saved') - out.Check(r'Base TimeStamp Module', 'module list present') - pipe_name_escaped = pipe_name.replace('\\', '\\\\') - out.Check(r'CommandLine: *\'.*crashy_program\.exe *' + pipe_name_escaped, - 'some PEB data is correct') - out.Check(r'SystemRoot=C:\\Windows', 'some of environment captured', - re.IGNORECASE) + This is used as a drop-in replacement for os.path.exists() and os.access() + to test for the pipe's existence. Both of those calls tickle the pipe in a + way that appears to the server to be a client connecting, triggering error + messages when no data is received. - out = CdbRun(cdb_path, dump_path, '?? @$peb->ProcessParameters') - out.Check(r' ImagePathName *: _UNICODE_STRING ".*\\crashy_program\.exe"', - 'PEB->ProcessParameters.ImagePathName string captured') - out.Check(' DesktopInfo *: ' - '_UNICODE_STRING "(?!--- memory read error at address ).*"', - 'PEB->ProcessParameters.DesktopInfo string captured') + Although this function only needs to test pipe existence (waiting for + CreateNamedPipe()), it actually winds up testing pipe readiness (waiting for + ConnectNamedPipe()). This is unnecessary but harmless. + """ + try: + win32pipe.WaitNamedPipe(pipe_name, win32pipe.NMPWAIT_WAIT_FOREVER) + except pywintypes.error as e: + if e[0] == winerror.ERROR_FILE_NOT_FOUND: + return False + raise + return True - out = CdbRun(cdb_path, dump_path, '!teb') - out.Check(r'TEB at', 'found the TEB') - out.Check(r'ExceptionList:\s+[0-9a-fA-F]+', 'some valid teb data') - out.Check(r'LastErrorValue:\s+2', 'correct LastErrorValue') - out = CdbRun(cdb_path, dump_path, '!gle') - out.Check('LastErrorValue: \(Win32\) 0x2 \(2\) - The system cannot find the ' - 'file specified.', '!gle gets last error') - out.Check('LastStatusValue: \(NTSTATUS\) 0xc000000f - {File Not Found} The ' - 'file %hs does not exist.', '!gle gets last ntstatus') +def GetDumpFromProgram(out_dir, pipe_name, executable_name, expect_exit_code, + *args): + """Initialize a crash database, and run |executable_name| connecting to a + crash handler. If pipe_name is set, crashpad_handler will be started first. + If pipe_name is empty, the executable is responsible for starting + crashpad_handler. *args will be passed after other arguments to + executable_name. If the child process does not exit with |expect_exit_code|, + an exception will be raised. Returns the path to the minidump generated by + crashpad_handler for further testing. + """ + test_database = MakeTempDir() + handler = None - if False: - # TODO(scottmg): Re-enable when we grab ntdll!RtlCriticalSectionList. - out = CdbRun(cdb_path, dump_path, '!locks') - out.Check(r'CritSec crashy_program!crashpad::`anonymous namespace\'::' - r'g_test_critical_section', 'lock was captured') - if platform.win32_ver()[0] != '7': - # We can't allocate CRITICAL_SECTIONs with .DebugInfo on Win 7. - out.Check(r'\*\*\* Locked', 'lock debug info was captured, and is locked') + try: + subprocess.check_call([ + os.path.join(out_dir, 'crashpad_database_util.exe'), '--create', + '--database=' + test_database + ]) - out = CdbRun(cdb_path, dump_path, '!handle') - out.Check(r'\d+ Handles', 'captured handles') - out.Check(r'Event\s+\d+', 'capture some event handles') - out.Check(r'File\s+\d+', 'capture some file handles') + if pipe_name is not None: + handler = subprocess.Popen([ + os.path.join(out_dir, 'crashpad_handler.com'), + '--pipe-name=' + pipe_name, '--database=' + test_database + ]) - out = CdbRun(cdb_path, dump_path, 'lm') - out.Check(r'Unloaded modules:', 'captured some unloaded modules') - out.Check(r'lz32\.dll', 'found expected unloaded module lz32') - out.Check(r'wmerror\.dll', 'found expected unloaded module wmerror') + # Wait until the server is ready. + printed = False + while not NamedPipeExistsAndReady(pipe_name): + if not printed: + print('Waiting for crashpad_handler to be ready...') + printed = True + time.sleep(0.001) - out = CdbRun(cdb_path, destroyed_dump_path, '.ecxr;!peb;k 2') - out.Check(r'Ldr\.InMemoryOrderModuleList:.*\d+ \. \d+', 'PEB_LDR_DATA saved') - out.Check(r'ntdll\.dll', 'ntdll present', re.IGNORECASE) + command = [os.path.join(out_dir, executable_name), pipe_name + ] + list(args) + else: + command = ([ + os.path.join(out_dir, executable_name), + os.path.join(out_dir, 'crashpad_handler.com'), test_database + ] + list(args)) + print('Running %s' % os.path.basename(command[0])) + exit_code = subprocess.call(command) + if exit_code != expect_exit_code: + raise subprocess.CalledProcessError(exit_code, executable_name) - # Check that there is no stack trace in the self-destroyed process. Confirm - # that the top is where we expect it (that's based only on IP), but subsequent - # stack entries will not be available. This confirms that we have a mostly - # valid dump, but that the stack was omitted. - out.Check(r'self_destroying_program!crashpad::`anonymous namespace\'::' - r'FreeOwnStackAndBreak.*\nquit:', - 'at correct location, no additional stack entries') + out = subprocess.check_output([ + os.path.join(out_dir, 'crashpad_database_util.exe'), + '--database=' + test_database, + '--show-pending-reports', + '--show-all-report-info', + ]) + for line in out.splitlines(): + if line.strip().startswith('Path:'): + return line.partition(':')[2].strip() + finally: + if handler: + handler.kill() - # Dump memory pointed to be EDI on the background suspended thread. We don't - # know the index of the thread because the system may have started other - # threads, so first do a run to extract the thread index that's suspended, and - # then another run to dump the data pointed to by EDI for that thread. - out = CdbRun(cdb_path, dump_path, '.ecxr;~') - match_obj = out.Find(r'(\d+)\s+Id: [0-9a-f.]+ Suspend: 1 Teb:') - if match_obj: - thread = match_obj.group(1) - out = CdbRun(cdb_path, dump_path, '.ecxr;~' + thread + 's;db /c14 edi') - out.Check(r'63 62 61 60 5f 5e 5d 5c-5b 5a 59 58 57 56 55 54 53 52 51 50', - 'data pointed to by registers captured') - # Move up one stack frame after jumping to the exception, and examine memory. - out = CdbRun(cdb_path, dump_path, - '.ecxr; .f+; dd /c100 poi(offset_pointer)-20') - out.Check(r'80000078 00000079 8000007a 0000007b 8000007c 0000007d 8000007e ' - r'0000007f 80000080 00000081 80000082 00000083 80000084 00000085 ' - r'80000086 00000087 80000088 00000089 8000008a 0000008b 8000008c ' - r'0000008d 8000008e 0000008f 80000090 00000091 80000092 00000093 ' - r'80000094 00000095 80000096 00000097', - 'data pointed to by stack captured') +def GetDumpFromCrashyProgram(out_dir, pipe_name): + return GetDumpFromProgram(out_dir, pipe_name, 'crashy_program.exe', + win32con.EXCEPTION_ACCESS_VIOLATION) - # Attempt to retrieve the value of g_extra_memory_pointer (by name), and then - # examine the memory at which it points. Both should have been saved. - out = CdbRun(cdb_path, dump_path, - 'dd poi(crashy_program!crashpad::g_extra_memory_pointer)+0x1f30 ' - 'L8') - out.Check(r'0000655e 0000656b 00006578 00006585', - 'extra memory range captured') - out.Check(r'\?\?\?\?\?\?\?\? \?\?\?\?\?\?\?\? ' - r'\?\?\?\?\?\?\?\? \?\?\?\?\?\?\?\?', - ' and not memory after range') - if False: - # TODO(scottmg): This is flakily capturing too much memory in Debug builds, - # possibly because a stale pointer is being captured via the stack. - # See: https://bugs.chromium.org/p/crashpad/issues/detail?id=101. - out = CdbRun(cdb_path, dump_path, - 'dd poi(crashy_program!crashpad::g_extra_memory_not_saved)' - '+0x1f30 L4') - # We save only the pointer, not the pointed-to data. If the pointer itself - # wasn't saved, then we won't get any memory printed, so here we're - # confirming the pointer was saved but the memory wasn't. - out.Check(r'\?\?\?\?\?\?\?\? \?\?\?\?\?\?\?\? ' - r'\?\?\?\?\?\?\?\? \?\?\?\?\?\?\?\?', - 'extra memory removal') +def GetDumpFromOtherProgram(out_dir, pipe_name, *args): + return GetDumpFromProgram(out_dir, pipe_name, 'crash_other_program.exe', 0, + *args) - out = CdbRun(cdb_path, dump_path, '.dumpdebug') - out.Check(r'type \?\?\? \(333333\), size 00001000', - 'first user stream') - out.Check(r'type \?\?\? \(222222\), size 00000080', - 'second user stream') - if z7_dump_path: - out = CdbRun(cdb_path, z7_dump_path, '.ecxr;lm') +def GetDumpFromSignal(out_dir, pipe_name, *args): + STATUS_FATAL_APP_EXIT = 0x40000015 # Not known by win32con. + return GetDumpFromProgram(out_dir, pipe_name, 'crashy_signal.exe', + STATUS_FATAL_APP_EXIT, *args) + + +def GetDumpFromSelfDestroyingProgram(out_dir, pipe_name): + return GetDumpFromProgram(out_dir, pipe_name, 'self_destroying_program.exe', + win32con.EXCEPTION_BREAKPOINT) + + +def GetDumpFromZ7Program(out_dir, pipe_name): + return GetDumpFromProgram(out_dir, pipe_name, 'crashy_z7_loader.exe', + win32con.EXCEPTION_ACCESS_VIOLATION) + + +class CdbRun(object): + """Run cdb.exe passing it a cdb command and capturing the output. + `Check()` searches for regex patterns in sequence allowing verification of + expected output. + """ + + def __init__(self, cdb_path, dump_path, command): + # Run a command line that loads the dump, runs the specified cdb + # command, and then quits, and capturing stdout. + self.out = subprocess.check_output( + [cdb_path, '-z', dump_path, '-c', command + ';q']) + + def Check(self, pattern, message, re_flags=0): + match_obj = re.search(pattern, self.out, re_flags) + if match_obj: + # Matched. Consume up to end of match. + self.out = self.out[match_obj.end(0):] + print('ok - %s' % message) + sys.stdout.flush() + else: + print('-' * 80, file=sys.stderr) + print('FAILED - %s' % message, file=sys.stderr) + print('-' * 80, file=sys.stderr) + print('did not match:\n %s' % pattern, file=sys.stderr) + print('-' * 80, file=sys.stderr) + print('remaining output was:\n %s' % self.out, file=sys.stderr) + print('-' * 80, file=sys.stderr) + sys.stderr.flush() + global g_had_failures + g_had_failures = True + + def Find(self, pattern, re_flags=0): + match_obj = re.search(pattern, self.out, re_flags) + if match_obj: + # Matched. Consume up to end of match. + self.out = self.out[match_obj.end(0):] + return match_obj + return None + + +def RunTests(cdb_path, dump_path, start_handler_dump_path, destroyed_dump_path, + z7_dump_path, other_program_path, other_program_no_exception_path, + sigabrt_main_path, sigabrt_background_path, pipe_name): + """Runs various tests in sequence. Runs a new cdb instance on the dump for + each block of tests to reduce the chances that output from one command is + confused for output from another. + """ + out = CdbRun(cdb_path, dump_path, '.ecxr') out.Check('This dump file has an exception of interest stored in it', - 'captured exception in z7 module') - # Older versions of cdb display relative to exports for /Z7 modules, newer - # ones just display the offset. - out.Check(r'z7_test(!CrashMe\+0xe|\+0x100e):', - 'exception in z7 at correct location') - out.Check(r'z7_test C \(codeview symbols\) z7_test\.dll', - 'expected non-pdb symbol format') + 'captured exception') - out = CdbRun(cdb_path, other_program_path, '.ecxr;k;~') - out.Check('Unknown exception - code deadbea7', - 'other program dump exception code') - out.Check('!Sleep', 'other program reasonable location') - out.Check("hanging_program!`anonymous namespace'::Thread1", - 'other program dump right thread') - count = 0 - while True: - match_obj = out.Find(r'Id.*Suspend: (\d+) ') + # When SomeCrashyFunction is inlined, cdb doesn't demangle its namespace as + # "`anonymous namespace'" and instead gives the decorated form. + out.Check( + 'crashy_program!crashpad::(`anonymous namespace\'|\?A0x[0-9a-f]+)::' + 'SomeCrashyFunction', 'exception at correct location') + + out = CdbRun(cdb_path, start_handler_dump_path, '.ecxr') + out.Check('This dump file has an exception of interest stored in it', + 'captured exception (using StartHandler())') + out.Check( + 'crashy_program!crashpad::(`anonymous namespace\'|\?A0x[0-9a-f]+)::' + 'SomeCrashyFunction', + 'exception at correct location (using StartHandler())') + + out = CdbRun(cdb_path, dump_path, '!peb') + out.Check(r'PEB at', 'found the PEB') + out.Check(r'Ldr\.InMemoryOrderModuleList:.*\d+ \. \d+', + 'PEB_LDR_DATA saved') + out.Check(r'Base TimeStamp Module', + 'module list present') + pipe_name_escaped = pipe_name.replace('\\', '\\\\') + out.Check(r'CommandLine: *\'.*crashy_program\.exe *' + pipe_name_escaped, + 'some PEB data is correct') + out.Check(r'SystemRoot=C:\\Windows', 'some of environment captured', + re.IGNORECASE) + + out = CdbRun(cdb_path, dump_path, '?? @$peb->ProcessParameters') + out.Check(r' ImagePathName *: _UNICODE_STRING ".*\\crashy_program\.exe"', + 'PEB->ProcessParameters.ImagePathName string captured') + out.Check( + ' DesktopInfo *: ' + '_UNICODE_STRING "(?!--- memory read error at address ).*"', + 'PEB->ProcessParameters.DesktopInfo string captured') + + out = CdbRun(cdb_path, dump_path, '!teb') + out.Check(r'TEB at', 'found the TEB') + out.Check(r'ExceptionList:\s+[0-9a-fA-F]+', 'some valid teb data') + out.Check(r'LastErrorValue:\s+2', 'correct LastErrorValue') + + out = CdbRun(cdb_path, dump_path, '!gle') + out.Check( + 'LastErrorValue: \(Win32\) 0x2 \(2\) - The system cannot find the ' + 'file specified.', '!gle gets last error') + out.Check( + 'LastStatusValue: \(NTSTATUS\) 0xc000000f - {File Not Found} The ' + 'file %hs does not exist.', '!gle gets last ntstatus') + + if False: + # TODO(scottmg): Re-enable when we grab ntdll!RtlCriticalSectionList. + out = CdbRun(cdb_path, dump_path, '!locks') + out.Check( + r'CritSec crashy_program!crashpad::`anonymous namespace\'::' + r'g_test_critical_section', 'lock was captured') + if platform.win32_ver()[0] != '7': + # We can't allocate CRITICAL_SECTIONs with .DebugInfo on Win 7. + out.Check(r'\*\*\* Locked', + 'lock debug info was captured, and is locked') + + out = CdbRun(cdb_path, dump_path, '!handle') + out.Check(r'\d+ Handles', 'captured handles') + out.Check(r'Event\s+\d+', 'capture some event handles') + out.Check(r'File\s+\d+', 'capture some file handles') + + out = CdbRun(cdb_path, dump_path, 'lm') + out.Check(r'Unloaded modules:', 'captured some unloaded modules') + out.Check(r'lz32\.dll', 'found expected unloaded module lz32') + out.Check(r'wmerror\.dll', 'found expected unloaded module wmerror') + + out = CdbRun(cdb_path, destroyed_dump_path, '.ecxr;!peb;k 2') + out.Check(r'Ldr\.InMemoryOrderModuleList:.*\d+ \. \d+', + 'PEB_LDR_DATA saved') + out.Check(r'ntdll\.dll', 'ntdll present', re.IGNORECASE) + + # Check that there is no stack trace in the self-destroyed process. Confirm + # that the top is where we expect it (that's based only on IP), but + # subsequent stack entries will not be available. This confirms that we have + # a mostly valid dump, but that the stack was omitted. + out.Check( + r'self_destroying_program!crashpad::`anonymous namespace\'::' + r'FreeOwnStackAndBreak.*\nquit:', + 'at correct location, no additional stack entries') + + # Dump memory pointed to be EDI on the background suspended thread. We don't + # know the index of the thread because the system may have started other + # threads, so first do a run to extract the thread index that's suspended, + # and then another run to dump the data pointed to by EDI for that thread. + out = CdbRun(cdb_path, dump_path, '.ecxr;~') + match_obj = out.Find(r'(\d+)\s+Id: [0-9a-f.]+ Suspend: 1 Teb:') if match_obj: - if match_obj.group(1) != '0': - out.Check(r'FAILED', 'all suspend counts should be 0') - else: - count += 1 - else: - break - assert count > 2 + thread = match_obj.group(1) + out = CdbRun(cdb_path, dump_path, '.ecxr;~' + thread + 's;db /c14 edi') + out.Check(r'63 62 61 60 5f 5e 5d 5c-5b 5a 59 58 57 56 55 54 53 52 51 50', + 'data pointed to by registers captured') - out = CdbRun(cdb_path, other_program_no_exception_path, '.ecxr;k') - out.Check('Unknown exception - code 0cca11ed', - 'other program with no exception given') - out.Check('!RaiseException', 'other program in RaiseException()') + # Move up one stack frame after jumping to the exception, and examine + # memory. + out = CdbRun(cdb_path, dump_path, + '.ecxr; .f+; dd /c100 poi(offset_pointer)-20') + out.Check( + r'80000078 00000079 8000007a 0000007b 8000007c 0000007d 8000007e ' + r'0000007f 80000080 00000081 80000082 00000083 80000084 00000085 ' + r'80000086 00000087 80000088 00000089 8000008a 0000008b 8000008c ' + r'0000008d 8000008e 0000008f 80000090 00000091 80000092 00000093 ' + r'80000094 00000095 80000096 00000097', + 'data pointed to by stack captured') - out = CdbRun(cdb_path, sigabrt_main_path, '.ecxr') - out.Check('code 40000015', 'got sigabrt signal') - out.Check('::HandleAbortSignal', ' stack in expected location') + # Attempt to retrieve the value of g_extra_memory_pointer (by name), and + # then examine the memory at which it points. Both should have been saved. + out = CdbRun( + cdb_path, dump_path, + 'dd poi(crashy_program!crashpad::g_extra_memory_pointer)+0x1f30 ' + 'L8') + out.Check(r'0000655e 0000656b 00006578 00006585', + 'extra memory range captured') - out = CdbRun(cdb_path, sigabrt_background_path, '.ecxr') - out.Check('code 40000015', 'got sigabrt signal from background thread') + out = CdbRun(cdb_path, dump_path, '.dumpdebug') + out.Check(r'type \?\?\? \(333333\), size 00001000', 'first user stream') + out.Check(r'type \?\?\? \(222222\), size 00000080', 'second user stream') + + if z7_dump_path: + out = CdbRun(cdb_path, z7_dump_path, '.ecxr;lm') + out.Check('This dump file has an exception of interest stored in it', + 'captured exception in z7 module') + # Older versions of cdb display relative to exports for /Z7 modules, + # newer ones just display the offset. + out.Check(r'z7_test(!CrashMe\+0xe|\+0x100e):', + 'exception in z7 at correct location') + out.Check(r'z7_test C \(codeview symbols\) z7_test\.dll', + 'expected non-pdb symbol format') + + out = CdbRun(cdb_path, other_program_path, '.ecxr;k;~') + out.Check('Unknown exception - code deadbea7', + 'other program dump exception code') + out.Check('!Sleep', 'other program reasonable location') + out.Check("hanging_program!`anonymous namespace'::Thread1", + 'other program dump right thread') + count = 0 + while True: + match_obj = out.Find(r'Id.*Suspend: (\d+) ') + if match_obj: + if match_obj.group(1) != '0': + out.Check(r'FAILED', 'all suspend counts should be 0') + else: + count += 1 + else: + break + assert count > 2 + + out = CdbRun(cdb_path, other_program_no_exception_path, '.ecxr;k') + out.Check('Unknown exception - code 0cca11ed', + 'other program with no exception given') + out.Check('!RaiseException', 'other program in RaiseException()') + + out = CdbRun(cdb_path, sigabrt_main_path, '.ecxr') + out.Check('code 40000015', 'got sigabrt signal') + out.Check('::HandleAbortSignal', ' stack in expected location') + + out = CdbRun(cdb_path, sigabrt_background_path, '.ecxr') + out.Check('code 40000015', 'got sigabrt signal from background thread') def main(args): - try: - if len(args) != 1: - print('must supply binary dir', file=sys.stderr) - return 1 + try: + if len(args) != 1: + print('must supply binary dir', file=sys.stderr) + return 1 - cdb_path = GetCdbPath() - if not cdb_path: - print('could not find cdb', file=sys.stderr) - return 1 + cdb_path = GetCdbPath() + if not cdb_path: + print('could not find cdb', file=sys.stderr) + return 1 - # Make sure we can download Windows symbols. - if not os.environ.get('_NT_SYMBOL_PATH'): - symbol_dir = MakeTempDir() - protocol = 'https' if platform.win32_ver()[0] != 'XP' else 'http' - os.environ['_NT_SYMBOL_PATH'] = ( - 'SRV*' + symbol_dir + '*' + - protocol + '://msdl.microsoft.com/download/symbols') + # Make sure we can download Windows symbols. + if not os.environ.get('_NT_SYMBOL_PATH'): + symbol_dir = MakeTempDir() + protocol = 'https' if platform.win32_ver()[0] != 'XP' else 'http' + os.environ['_NT_SYMBOL_PATH'] = ( + 'SRV*' + symbol_dir + '*' + protocol + + '://msdl.microsoft.com/download/symbols') - pipe_name = r'\\.\pipe\end-to-end_%s_%s' % ( - os.getpid(), str(random.getrandbits(64))) + pipe_name = r'\\.\pipe\end-to-end_%s_%s' % (os.getpid(), + str(random.getrandbits(64))) - crashy_dump_path = GetDumpFromCrashyProgram(args[0], pipe_name) - if not crashy_dump_path: - return 1 + crashy_dump_path = GetDumpFromCrashyProgram(args[0], pipe_name) + if not crashy_dump_path: + return 1 - start_handler_dump_path = GetDumpFromCrashyProgram(args[0], None) - if not start_handler_dump_path: - return 1 + start_handler_dump_path = GetDumpFromCrashyProgram(args[0], None) + if not start_handler_dump_path: + return 1 - destroyed_dump_path = GetDumpFromSelfDestroyingProgram(args[0], pipe_name) - if not destroyed_dump_path: - return 1 + destroyed_dump_path = GetDumpFromSelfDestroyingProgram( + args[0], pipe_name) + if not destroyed_dump_path: + return 1 - z7_dump_path = None - if not args[0].endswith('_x64'): - z7_dump_path = GetDumpFromZ7Program(args[0], pipe_name) - if not z7_dump_path: - return 1 + z7_dump_path = None + if not args[0].endswith('_x64'): + z7_dump_path = GetDumpFromZ7Program(args[0], pipe_name) + if not z7_dump_path: + return 1 - other_program_path = GetDumpFromOtherProgram(args[0], pipe_name) - if not other_program_path: - return 1 + other_program_path = GetDumpFromOtherProgram(args[0], pipe_name) + if not other_program_path: + return 1 - other_program_no_exception_path = GetDumpFromOtherProgram( - args[0], pipe_name, 'noexception') - if not other_program_no_exception_path: - return 1 + other_program_no_exception_path = GetDumpFromOtherProgram( + args[0], pipe_name, 'noexception') + if not other_program_no_exception_path: + return 1 - sigabrt_main_path = GetDumpFromSignal(args[0], pipe_name, 'main') - if not sigabrt_main_path: - return 1 + sigabrt_main_path = GetDumpFromSignal(args[0], pipe_name, 'main') + if not sigabrt_main_path: + return 1 - sigabrt_background_path = GetDumpFromSignal( - args[0], pipe_name, 'background') - if not sigabrt_background_path: - return 1 + sigabrt_background_path = GetDumpFromSignal(args[0], pipe_name, + 'background') + if not sigabrt_background_path: + return 1 - RunTests(cdb_path, - crashy_dump_path, - start_handler_dump_path, - destroyed_dump_path, - z7_dump_path, - other_program_path, - other_program_no_exception_path, - sigabrt_main_path, - sigabrt_background_path, - pipe_name) + RunTests(cdb_path, crashy_dump_path, start_handler_dump_path, + destroyed_dump_path, z7_dump_path, other_program_path, + other_program_no_exception_path, sigabrt_main_path, + sigabrt_background_path, pipe_name) - return 1 if g_had_failures else 0 - finally: - CleanUpTempDirs() + return 1 if g_had_failures else 0 + finally: + CleanUpTempDirs() if __name__ == '__main__': - sys.exit(main(sys.argv[1:])) + sys.exit(main(sys.argv[1:])) diff --git a/snapshot/win/exception_snapshot_win.cc b/snapshot/win/exception_snapshot_win.cc index 64b53424..3413a403 100644 --- a/snapshot/win/exception_snapshot_win.cc +++ b/snapshot/win/exception_snapshot_win.cc @@ -17,9 +17,9 @@ #include "client/crashpad_client.h" #include "snapshot/capture_memory.h" #include "snapshot/memory_snapshot.h" -#include "snapshot/win/cpu_context_win.h" +#include "snapshot/memory_snapshot_generic.h" #include "snapshot/win/capture_memory_delegate_win.h" -#include "snapshot/win/memory_snapshot_win.h" +#include "snapshot/win/cpu_context_win.h" #include "snapshot/win/process_reader_win.h" #include "util/win/nt_internals.h" @@ -28,22 +28,13 @@ namespace internal { namespace { +#if defined(ARCH_CPU_X86_FAMILY) #if defined(ARCH_CPU_32_BITS) using Context32 = CONTEXT; #elif defined(ARCH_CPU_64_BITS) using Context32 = WOW64_CONTEXT; #endif -#if defined(ARCH_CPU_64_BITS) -void NativeContextToCPUContext64(const CONTEXT& context_record, - CPUContext* context, - CPUContextUnion* context_union) { - context->architecture = kCPUArchitectureX86_64; - context->x86_64 = &context_union->x86_64; - InitializeX64Context(context_record, context->x86_64); -} -#endif - void NativeContextToCPUContext32(const Context32& context_record, CPUContext* context, CPUContextUnion* context_union) { @@ -51,6 +42,25 @@ void NativeContextToCPUContext32(const Context32& context_record, context->x86 = &context_union->x86; InitializeX86Context(context_record, context->x86); } +#endif // ARCH_CPU_X86_FAMILY + +#if defined(ARCH_CPU_64_BITS) +void NativeContextToCPUContext64(const CONTEXT& context_record, + CPUContext* context, + CPUContextUnion* context_union) { +#if defined(ARCH_CPU_X86_64) + context->architecture = kCPUArchitectureX86_64; + context->x86_64 = &context_union->x86_64; + InitializeX64Context(context_record, context->x86_64); +#elif defined(ARCH_CPU_ARM64) + context->architecture = kCPUArchitectureARM64; + context->arm64 = &context_union->arm64; + InitializeARM64Context(context_record, context->arm64); +#else +#error Unsupported Windows 64-bit Arch +#endif +} +#endif } // namespace @@ -106,6 +116,8 @@ bool ExceptionSnapshotWin::Initialize( } } #endif + +#if !defined(ARCH_CPU_ARM64) if (!is_64_bit) { if (!InitializeFromExceptionPointers( @@ -116,6 +128,7 @@ bool ExceptionSnapshotWin::Initialize( return false; } } +#endif CaptureMemoryDelegateWin capture_memory_delegate( process_reader, *thread, &extra_memory_, nullptr); @@ -176,9 +189,9 @@ bool ExceptionSnapshotWin::InitializeFromExceptionPointers( CPUContext* context, CPUContextUnion* context_union)) { ExceptionPointersType exception_pointers; - if (!process_reader->ReadMemory(exception_pointers_address, - sizeof(exception_pointers), - &exception_pointers)) { + if (!process_reader->Memory()->Read(exception_pointers_address, + sizeof(exception_pointers), + &exception_pointers)) { LOG(ERROR) << "EXCEPTION_POINTERS read failed"; return false; } @@ -188,7 +201,7 @@ bool ExceptionSnapshotWin::InitializeFromExceptionPointers( } ExceptionRecordType first_record; - if (!process_reader->ReadMemory( + if (!process_reader->Memory()->Read( static_cast(exception_pointers.ExceptionRecord), sizeof(first_record), &first_record)) { @@ -240,7 +253,7 @@ bool ExceptionSnapshotWin::InitializeFromExceptionPointers( } ContextType context_record; - if (!process_reader->ReadMemory( + if (!process_reader->Memory()->Read( static_cast(exception_pointers.ContextRecord), sizeof(context_record), &context_record)) { diff --git a/snapshot/win/exception_snapshot_win.h b/snapshot/win/exception_snapshot_win.h index f6e29d9f..fd4e8543 100644 --- a/snapshot/win/exception_snapshot_win.h +++ b/snapshot/win/exception_snapshot_win.h @@ -36,14 +36,16 @@ class ProcessReaderWin; namespace internal { -class MemorySnapshotWin; +class MemorySnapshotGeneric; -#if defined(ARCH_CPU_X86_FAMILY) union CPUContextUnion { +#if defined(ARCH_CPU_X86_FAMILY) CPUContextX86 x86; CPUContextX86_64 x86_64; -}; +#elif defined(ARCH_CPU_ARM64) + CPUContextARM64 arm64; #endif +}; class ExceptionSnapshotWin final : public ExceptionSnapshot { public: @@ -91,12 +93,10 @@ class ExceptionSnapshotWin final : public ExceptionSnapshot { CPUContext* context, CPUContextUnion* context_union)); -#if defined(ARCH_CPU_X86_FAMILY) CPUContextUnion context_union_; -#endif CPUContext context_; std::vector codes_; - std::vector> extra_memory_; + std::vector> extra_memory_; uint64_t thread_id_; uint64_t exception_address_; uint32_t exception_flags_; diff --git a/snapshot/win/exception_snapshot_win_test.cc b/snapshot/win/exception_snapshot_win_test.cc index 537b4ca8..07a25a89 100644 --- a/snapshot/win/exception_snapshot_win_test.cc +++ b/snapshot/win/exception_snapshot_win_test.cc @@ -23,7 +23,6 @@ #include "gtest/gtest.h" #include "snapshot/win/process_snapshot_win.h" #include "test/errors.h" -#include "test/gtest_disabled.h" #include "test/test_paths.h" #include "test/win/child_launcher.h" #include "util/file/file_io.h" @@ -103,7 +102,12 @@ 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(). +#if !defined(NDEBUG) + // Debug build is likely not optimized and contains more instructions. + constexpr uint64_t kAllowedOffset = 200; +#else constexpr uint64_t kAllowedOffset = 100; +#endif EXPECT_GT(snapshot.Exception()->ExceptionAddress(), break_near_); EXPECT_LT(snapshot.Exception()->ExceptionAddress(), break_near_ + kAllowedOffset); @@ -176,7 +180,7 @@ TEST(ExceptionSnapshotWinTest, MAYBE_ChildCrash) { #if defined(ARCH_CPU_64_BITS) TEST(ExceptionSnapshotWinTest, ChildCrashWOW64) { if (!TestPaths::Has32BitBuildArtifacts()) { - DISABLED_TEST(); + GTEST_SKIP(); } TestCrashingChild(TestPaths::Architecture::k32Bit); @@ -214,6 +218,9 @@ class SimulateDelegate : public ExceptionHandlerServer::Delegate { // ASan instrumentation inserts more instructions between the expected // location and what's reported. https://crbug.com/845011. constexpr uint64_t kAllowedOffset = 500; +#elif !defined(NDEBUG) + // Debug build is likely not optimized and contains more instructions. + constexpr uint64_t kAllowedOffset = 200; #else constexpr uint64_t kAllowedOffset = 100; #endif @@ -280,14 +287,20 @@ void TestDumpWithoutCrashingChild(TestPaths::Architecture architecture) { EXPECT_EQ(child.WaitForExit(), 0u); } -TEST(SimulateCrash, ChildDumpWithoutCrashing) { +#if defined(ADDRESS_SANITIZER) +// https://crbug.com/845011 +#define MAYBE_ChildDumpWithoutCrashing DISABLED_ChildDumpWithoutCrashing +#else +#define MAYBE_ChildDumpWithoutCrashing ChildDumpWithoutCrashing +#endif +TEST(SimulateCrash, MAYBE_ChildDumpWithoutCrashing) { TestDumpWithoutCrashingChild(TestPaths::Architecture::kDefault); } #if defined(ARCH_CPU_64_BITS) TEST(SimulateCrash, ChildDumpWithoutCrashingWOW64) { if (!TestPaths::Has32BitBuildArtifacts()) { - DISABLED_TEST(); + GTEST_SKIP(); } TestDumpWithoutCrashingChild(TestPaths::Architecture::k32Bit); diff --git a/snapshot/win/extra_memory_ranges_test.cc b/snapshot/win/extra_memory_ranges_test.cc index dcef8055..e0c17a20 100644 --- a/snapshot/win/extra_memory_ranges_test.cc +++ b/snapshot/win/extra_memory_ranges_test.cc @@ -25,7 +25,6 @@ #include "client/simple_address_range_bag.h" #include "gtest/gtest.h" #include "snapshot/win/process_snapshot_win.h" -#include "test/gtest_disabled.h" #include "test/test_paths.h" #include "test/win/child_launcher.h" #include "util/file/file_io.h" @@ -110,7 +109,7 @@ TEST(ExtraMemoryRanges, CrashDebugBreak) { #if defined(ARCH_CPU_64_BITS) TEST(ExtraMemoryRanges, DontCrashWOW64) { if (!TestPaths::Has32BitBuildArtifacts()) { - DISABLED_TEST(); + GTEST_SKIP(); } TestExtraMemoryRanges(kDontCrash, TestPaths::Architecture::k32Bit); @@ -118,7 +117,7 @@ TEST(ExtraMemoryRanges, DontCrashWOW64) { TEST(ExtraMemoryRanges, CrashDebugBreakWOW64) { if (!TestPaths::Has32BitBuildArtifacts()) { - DISABLED_TEST(); + GTEST_SKIP(); } TestExtraMemoryRanges(kCrashDebugBreak, TestPaths::Architecture::k32Bit); diff --git a/snapshot/win/memory_snapshot_win.cc b/snapshot/win/memory_snapshot_win.cc deleted file mode 100644 index 17e8ac11..00000000 --- a/snapshot/win/memory_snapshot_win.cc +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2015 The Crashpad Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include - -#include "snapshot/win/memory_snapshot_win.h" - -namespace crashpad { -namespace internal { - -MemorySnapshotWin::MemorySnapshotWin() - : MemorySnapshot(), - process_reader_(nullptr), - address_(0), - size_(0), - initialized_() { -} - -MemorySnapshotWin::~MemorySnapshotWin() { -} - -void MemorySnapshotWin::Initialize(ProcessReaderWin* process_reader, - uint64_t address, - uint64_t size) { - INITIALIZATION_STATE_SET_INITIALIZING(initialized_); - process_reader_ = process_reader; - address_ = address; - DLOG_IF(WARNING, size >= std::numeric_limits::max()) - << "size overflow"; - size_ = static_cast(size); - INITIALIZATION_STATE_SET_VALID(initialized_); -} - -uint64_t MemorySnapshotWin::Address() const { - INITIALIZATION_STATE_DCHECK_VALID(initialized_); - return address_; -} - -size_t MemorySnapshotWin::Size() const { - INITIALIZATION_STATE_DCHECK_VALID(initialized_); - return size_; -} - -bool MemorySnapshotWin::Read(Delegate* delegate) const { - INITIALIZATION_STATE_DCHECK_VALID(initialized_); - - if (size_ == 0) { - return delegate->MemorySnapshotDelegateRead(nullptr, size_); - } - - std::unique_ptr buffer(new uint8_t[size_]); - if (!process_reader_->ReadMemory(address_, size_, buffer.get())) { - return false; - } - return delegate->MemorySnapshotDelegateRead(buffer.get(), size_); -} - -const MemorySnapshot* MemorySnapshotWin::MergeWithOtherSnapshot( - const MemorySnapshot* other) const { - return MergeWithOtherSnapshotImpl(this, other); -} - -} // namespace internal -} // namespace crashpad diff --git a/snapshot/win/module_snapshot_win.cc b/snapshot/win/module_snapshot_win.cc index 880e9da0..c7aaad8d 100644 --- a/snapshot/win/module_snapshot_win.cc +++ b/snapshot/win/module_snapshot_win.cc @@ -19,7 +19,7 @@ #include "base/strings/utf_string_conversions.h" #include "client/crashpad_info.h" #include "client/simple_address_range_bag.h" -#include "snapshot/win/memory_snapshot_win.h" +#include "snapshot/memory_snapshot_generic.h" #include "snapshot/win/pe_image_annotations_reader.h" #include "snapshot/win/pe_image_reader.h" #include "util/misc/tri_state.h" @@ -33,17 +33,18 @@ ModuleSnapshotWin::ModuleSnapshotWin() name_(), pdb_name_(), uuid_(), - pe_image_reader_(), + memory_range_(), + streams_(), + vs_fixed_file_info_(), + initialized_vs_fixed_file_info_(), process_reader_(nullptr), + pe_image_reader_(), + crashpad_info_(), timestamp_(0), age_(0), - initialized_(), - vs_fixed_file_info_(), - initialized_vs_fixed_file_info_() { -} + initialized_() {} -ModuleSnapshotWin::~ModuleSnapshotWin() { -} +ModuleSnapshotWin::~ModuleSnapshotWin() {} bool ModuleSnapshotWin::Initialize( ProcessReaderWin* process_reader, @@ -75,6 +76,26 @@ bool ModuleSnapshotWin::Initialize( pdb_name_ = base::UTF16ToUTF8(name_); } + if (!memory_range_.Initialize(process_reader_->Memory(), + process_reader_->Is64Bit())) { + return false; + } + + WinVMAddress crashpad_info_address; + WinVMSize crashpad_info_size; + if (pe_image_reader_->GetCrashpadInfoSection(&crashpad_info_address, + &crashpad_info_size)) { + ProcessMemoryRange info_range; + info_range.Initialize(memory_range_); + info_range.RestrictRange(crashpad_info_address, + crashpad_info_address + crashpad_info_size); + + auto info = std::make_unique(); + if (info->Initialize(&info_range, crashpad_info_address)) { + crashpad_info_ = std::move(info); + } + } + INITIALIZATION_STATE_SET_VALID(initialized_); return true; } @@ -173,6 +194,11 @@ std::string ModuleSnapshotWin::DebugFileName() const { return pdb_name_; } +std::vector ModuleSnapshotWin::BuildID() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return std::vector(); +} + std::vector ModuleSnapshotWin::AnnotationsVector() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); // These correspond to system-logged things on Mac. We don't currently track @@ -228,8 +254,7 @@ ModuleSnapshotWin::CustomMinidumpStreams() const { template void ModuleSnapshotWin::GetCrashpadOptionsInternal( CrashpadInfoClientOptions* options) { - process_types::CrashpadInfo crashpad_info; - if (!pe_image_reader_->GetCrashpadInfo(&crashpad_info)) { + if (!crashpad_info_) { options->crashpad_handler_behavior = TriState::kUnset; options->system_crash_reporter_forwarding = TriState::kUnset; options->gather_indirectly_referenced_memory = TriState::kUnset; @@ -238,19 +263,13 @@ void ModuleSnapshotWin::GetCrashpadOptionsInternal( } options->crashpad_handler_behavior = - CrashpadInfoClientOptions::TriStateFromCrashpadInfo( - crashpad_info.crashpad_handler_behavior); - + crashpad_info_->CrashpadHandlerBehavior(); options->system_crash_reporter_forwarding = - CrashpadInfoClientOptions::TriStateFromCrashpadInfo( - crashpad_info.system_crash_reporter_forwarding); - + crashpad_info_->SystemCrashReporterForwarding(); options->gather_indirectly_referenced_memory = - CrashpadInfoClientOptions::TriStateFromCrashpadInfo( - crashpad_info.gather_indirectly_referenced_memory); - + crashpad_info_->GatherIndirectlyReferencedMemory(); options->indirectly_referenced_memory_cap = - crashpad_info.indirectly_referenced_memory_cap; + crashpad_info_->IndirectlyReferencedMemoryCap(); } const VS_FIXEDFILEINFO* ModuleSnapshotWin::VSFixedFileInfo() const { @@ -270,16 +289,13 @@ const VS_FIXEDFILEINFO* ModuleSnapshotWin::VSFixedFileInfo() const { template void ModuleSnapshotWin::GetCrashpadExtraMemoryRanges( std::set>* ranges) const { - process_types::CrashpadInfo crashpad_info; - if (!pe_image_reader_->GetCrashpadInfo(&crashpad_info) || - !crashpad_info.extra_address_ranges) { + if (!crashpad_info_ || !crashpad_info_->ExtraMemoryRanges()) return; - } std::vector simple_ranges( SimpleAddressRangeBag::num_entries); - if (!process_reader_->ReadMemory( - crashpad_info.extra_address_ranges, + if (!process_reader_->Memory()->Read( + crashpad_info_->ExtraMemoryRanges(), simple_ranges.size() * sizeof(simple_ranges[0]), &simple_ranges[0])) { LOG(WARNING) << "could not read simple address_ranges from " @@ -298,24 +314,23 @@ void ModuleSnapshotWin::GetCrashpadExtraMemoryRanges( template void ModuleSnapshotWin::GetCrashpadUserMinidumpStreams( std::vector>* streams) const { - process_types::CrashpadInfo crashpad_info; - if (!pe_image_reader_->GetCrashpadInfo(&crashpad_info)) + if (!crashpad_info_) return; - for (uint64_t cur = crashpad_info.user_data_minidump_stream_head; cur;) { + for (uint64_t cur = crashpad_info_->UserDataMinidumpStreamHead(); cur;) { internal::UserDataMinidumpStreamListEntry list_entry; - if (!process_reader_->ReadMemory( - cur, sizeof(list_entry), &list_entry)) { + if (!process_reader_->Memory()->Read( + cur, sizeof(list_entry), &list_entry)) { LOG(WARNING) << "could not read user data stream entry from " << base::UTF16ToUTF8(name_); return; } if (list_entry.size != 0) { - std::unique_ptr memory( - new internal::MemorySnapshotWin()); + std::unique_ptr memory( + new internal::MemorySnapshotGeneric()); memory->Initialize( - process_reader_, list_entry.base_address, list_entry.size); + process_reader_->Memory(), list_entry.base_address, list_entry.size); streams->push_back(std::make_unique( list_entry.stream_type, memory.release())); } diff --git a/snapshot/win/module_snapshot_win.h b/snapshot/win/module_snapshot_win.h index 693588db..b32216c3 100644 --- a/snapshot/win/module_snapshot_win.h +++ b/snapshot/win/module_snapshot_win.h @@ -25,6 +25,7 @@ #include "base/macros.h" #include "snapshot/crashpad_info_client_options.h" +#include "snapshot/crashpad_types/crashpad_info_reader.h" #include "snapshot/module_snapshot.h" #include "snapshot/win/process_reader_win.h" #include "util/misc/initialization_state.h" @@ -83,6 +84,7 @@ class ModuleSnapshotWin final : public ModuleSnapshot { ModuleType GetModuleType() const override; void UUIDAndAge(crashpad::UUID* uuid, uint32_t* age) const override; std::string DebugFileName() const override; + std::vector BuildID() const override; std::vector AnnotationsVector() const override; std::map AnnotationsSimpleMap() const override; std::vector AnnotationObjects() const override; @@ -109,18 +111,19 @@ class ModuleSnapshotWin final : public ModuleSnapshot { std::wstring name_; std::string pdb_name_; UUID uuid_; - std::unique_ptr pe_image_reader_; - ProcessReaderWin* process_reader_; // weak - time_t timestamp_; - uint32_t age_; + ProcessMemoryRange memory_range_; // Too const-y: https://crashpad.chromium.org/bug/9. mutable std::vector> streams_; - InitializationStateDcheck initialized_; - // VSFixedFileInfo() is logically const, but updates these members on the // call. See https://crashpad.chromium.org/bug/9. mutable VS_FIXEDFILEINFO vs_fixed_file_info_; mutable InitializationState initialized_vs_fixed_file_info_; + ProcessReaderWin* process_reader_; // weak + std::unique_ptr pe_image_reader_; + std::unique_ptr crashpad_info_; + time_t timestamp_; + uint32_t age_; + InitializationStateDcheck initialized_; DISALLOW_COPY_AND_ASSIGN(ModuleSnapshotWin); }; diff --git a/snapshot/win/pe_image_annotations_reader_test.cc b/snapshot/win/module_snapshot_win_test.cc similarity index 87% rename from snapshot/win/pe_image_annotations_reader_test.cc rename to snapshot/win/module_snapshot_win_test.cc index e1026008..d9786cfd 100644 --- a/snapshot/win/pe_image_annotations_reader_test.cc +++ b/snapshot/win/module_snapshot_win_test.cc @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "snapshot/win/pe_image_annotations_reader.h" +#include "snapshot/win/module_snapshot_win.h" #include #include @@ -30,7 +30,6 @@ #include "snapshot/annotation_snapshot.h" #include "snapshot/win/pe_image_reader.h" #include "snapshot/win/process_reader_win.h" -#include "test/gtest_disabled.h" #include "test/test_paths.h" #include "test/win/child_launcher.h" #include "util/file/file_io.h" @@ -74,20 +73,15 @@ void TestAnnotationsOnCrash(TestType type, std::map all_annotations_simple_map; std::vector all_annotation_objects; for (const ProcessInfo::Module& module : modules) { - PEImageReader pe_image_reader; - pe_image_reader.Initialize(&process_reader, - module.dll_base, - module.size, - base::UTF16ToUTF8(module.name)); - PEImageAnnotationsReader module_annotations_reader( - &process_reader, &pe_image_reader, module.name); + internal::ModuleSnapshotWin module_snapshot; + module_snapshot.Initialize(&process_reader, module); std::map module_annotations_simple_map = - module_annotations_reader.SimpleMap(); + module_snapshot.AnnotationsSimpleMap(); all_annotations_simple_map.insert(module_annotations_simple_map.begin(), module_annotations_simple_map.end()); - auto module_annotations_list = module_annotations_reader.AnnotationsList(); + auto module_annotations_list = module_snapshot.AnnotationObjects(); all_annotation_objects.insert(all_annotation_objects.end(), module_annotations_list.begin(), module_annotations_list.end()); @@ -146,26 +140,26 @@ void TestAnnotationsOnCrash(TestType type, EXPECT_EQ(child.WaitForExit(), expected_exit_code); } -TEST(PEImageAnnotationsReader, DontCrash) { +TEST(ModuleSnapshotWinTest, DontCrash) { TestAnnotationsOnCrash(kDontCrash, TestPaths::Architecture::kDefault); } -TEST(PEImageAnnotationsReader, CrashDebugBreak) { +TEST(ModuleSnapshotWinTest, CrashDebugBreak) { TestAnnotationsOnCrash(kCrashDebugBreak, TestPaths::Architecture::kDefault); } #if defined(ARCH_CPU_64_BITS) -TEST(PEImageAnnotationsReader, DontCrashWOW64) { +TEST(ModuleSnapshotWinTest, DontCrashWOW64) { if (!TestPaths::Has32BitBuildArtifacts()) { - DISABLED_TEST(); + GTEST_SKIP(); } TestAnnotationsOnCrash(kDontCrash, TestPaths::Architecture::k32Bit); } -TEST(PEImageAnnotationsReader, CrashDebugBreakWOW64) { +TEST(ModuleSnapshotWinTest, CrashDebugBreakWOW64) { if (!TestPaths::Has32BitBuildArtifacts()) { - DISABLED_TEST(); + GTEST_SKIP(); } TestAnnotationsOnCrash(kCrashDebugBreak, TestPaths::Architecture::k32Bit); diff --git a/snapshot/win/pe_image_annotations_reader.cc b/snapshot/win/pe_image_annotations_reader.cc index 67961457..058342db 100644 --- a/snapshot/win/pe_image_annotations_reader.cc +++ b/snapshot/win/pe_image_annotations_reader.cc @@ -17,6 +17,7 @@ #include #include +#include "base/stl_util.h" #include "base/strings/utf_string_conversions.h" #include "client/annotation.h" #include "client/simple_string_dictionary.h" @@ -92,7 +93,7 @@ void PEImageAnnotationsReader::ReadCrashpadSimpleAnnotations( std::vector simple_annotations(SimpleStringDictionary::num_entries); - if (!process_reader_->ReadMemory( + if (!process_reader_->Memory()->Read( crashpad_info.simple_annotations, simple_annotations.size() * sizeof(simple_annotations[0]), &simple_annotations[0])) { @@ -127,9 +128,9 @@ void PEImageAnnotationsReader::ReadCrashpadAnnotationsList( } process_types::AnnotationList annotation_list_object; - if (!process_reader_->ReadMemory(crashpad_info.annotations_list, - sizeof(annotation_list_object), - &annotation_list_object)) { + if (!process_reader_->Memory()->Read(crashpad_info.annotations_list, + sizeof(annotation_list_object), + &annotation_list_object)) { LOG(WARNING) << "could not read annotations list object in " << base::UTF16ToUTF8(name_); return; @@ -140,7 +141,7 @@ void PEImageAnnotationsReader::ReadCrashpadAnnotationsList( current.link_node != annotation_list_object.tail_pointer && index < kMaxNumberOfAnnotations; ++index) { - if (!process_reader_->ReadMemory( + if (!process_reader_->Memory()->Read( current.link_node, sizeof(current), ¤t)) { LOG(WARNING) << "could not read annotation at index " << index << " in " << base::UTF16ToUTF8(name_); @@ -155,7 +156,8 @@ void PEImageAnnotationsReader::ReadCrashpadAnnotationsList( snapshot.type = current.type; char name[Annotation::kNameMaxLength]; - if (!process_reader_->ReadMemory(current.name, arraysize(name), name)) { + if (!process_reader_->Memory()->Read( + current.name, base::size(name), name)) { LOG(WARNING) << "could not read annotation name at index " << index << " in " << base::UTF16ToUTF8(name_); continue; @@ -167,7 +169,7 @@ void PEImageAnnotationsReader::ReadCrashpadAnnotationsList( size_t value_length = std::min(static_cast(current.size), Annotation::kValueMaxSize); snapshot.value.resize(value_length); - if (!process_reader_->ReadMemory( + if (!process_reader_->Memory()->Read( current.value, value_length, snapshot.value.data())) { LOG(WARNING) << "could not read annotation value at index " << index << " in " << base::UTF16ToUTF8(name_); diff --git a/snapshot/win/pe_image_reader.cc b/snapshot/win/pe_image_reader.cc index e705bb51..e69faef0 100644 --- a/snapshot/win/pe_image_reader.cc +++ b/snapshot/win/pe_image_reader.cc @@ -21,6 +21,7 @@ #include #include "base/logging.h" +#include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "client/crashpad_info.h" #include "snapshot/win/pe_image_resource_reader.h" @@ -71,6 +72,18 @@ bool PEImageReader::Initialize(ProcessReaderWin* process_reader, return true; } +bool PEImageReader::GetCrashpadInfoSection(WinVMAddress* address, + WinVMSize* size) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + if (module_subrange_reader_.Is64Bit()) { + return GetCrashpadInfoSectionInternal( + address, size); + } else { + return GetCrashpadInfoSectionInternal( + address, size); + } +} + template bool PEImageReader::GetCrashpadInfo( process_types::CrashpadInfo* crashpad_info) const { @@ -275,7 +288,7 @@ bool PEImageReader::VSFixedFileInfo( version_info.wType != 0 || wcsncmp(version_info.szKey, L"VS_VERSION_INFO", - arraysize(version_info.szKey)) != 0) { + base::size(version_info.szKey)) != 0) { LOG(WARNING) << "unexpected VS_VERSIONINFO in " << module_subrange_reader_.name(); return false; @@ -293,6 +306,31 @@ bool PEImageReader::VSFixedFileInfo( return true; } +template +bool PEImageReader::GetCrashpadInfoSectionInternal(WinVMAddress* address, + WinVMSize* size) const { + IMAGE_SECTION_HEADER section; + if (!GetSectionByName::type>("CPADinfo", + §ion)) { + return false; + } + + process_types::CrashpadInfo crashpad_info; + 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; + } + + *address = Address() + section.VirtualAddress; + *size = std::min(sizeof(crashpad_info), section.Misc.VirtualSize); + + return true; +} + template bool PEImageReader::ReadNtHeaders(NtHeadersType* nt_headers, WinVMAddress* nt_headers_address) const { diff --git a/snapshot/win/pe_image_reader.h b/snapshot/win/pe_image_reader.h index 56a991b8..ebdbe578 100644 --- a/snapshot/win/pe_image_reader.h +++ b/snapshot/win/pe_image_reader.h @@ -94,6 +94,16 @@ class PEImageReader { //! This is the value passed as \a size to Initialize(). WinVMSize Size() const { return module_subrange_reader_.Size(); } + //! \brief Obtains the module's CrashpadInfo structure address and size. + //! + //! \param[out] address The CrashpadInfo structure address. + //! \param[out] size The CrashpadInfo structure size. + //! + //! \return `true` on success, `false` on failure. If the module does not have + //! a `CPADinfo` section, this will return `false` without logging any + //! messages. Other failures will result in messages being logged. + bool GetCrashpadInfoSection(WinVMAddress* address, WinVMSize* size) const; + //! \brief Obtains the module's CrashpadInfo structure. //! //! \return `true` on success, `false` on failure. If the module does not have @@ -137,6 +147,13 @@ class PEImageReader { bool VSFixedFileInfo(VS_FIXEDFILEINFO* vs_fixed_file_info) const; private: + //! \brief Performs the internal logic for GetCrashpadInfoSection(). + //! + //! \sa GetCrashpadInfoSection + template + bool GetCrashpadInfoSectionInternal(WinVMAddress* address, + WinVMSize* size) const; + //! \brief Reads the `IMAGE_NT_HEADERS` from the beginning of the image. //! //! \param[out] nt_headers The contents of the templated NtHeadersType diff --git a/snapshot/win/pe_image_reader_test.cc b/snapshot/win/pe_image_reader_test.cc index ac456e2d..161d9c8e 100644 --- a/snapshot/win/pe_image_reader_test.cc +++ b/snapshot/win/pe_image_reader_test.cc @@ -14,7 +14,9 @@ #include "snapshot/win/pe_image_reader.h" -#define PSAPI_VERSION 1 +#ifndef PSAPI_VERSION +#define PSAPI_VERSION 2 +#endif #include #include "base/files/file_path.h" diff --git a/snapshot/win/process_reader_win.cc b/snapshot/win/process_reader_win.cc index 2fa02584..51940c02 100644 --- a/snapshot/win/process_reader_win.cc +++ b/snapshot/win/process_reader_win.cc @@ -151,11 +151,17 @@ bool FillThreadContextAndSuspendCount(HANDLE thread_handle, PLOG(ERROR) << "SuspendThread"; return false; } - DCHECK(previous_suspend_count > 0 || - suspension_state == ProcessSuspensionState::kRunning); - thread->suspend_count = - previous_suspend_count - - (suspension_state == ProcessSuspensionState::kSuspended ? 1 : 0); + if (previous_suspend_count <= 0 && + suspension_state == ProcessSuspensionState::kSuspended) { + LOG(WARNING) << "Thread " << thread->id + << " should be suspended, but previous_suspend_count is " + << previous_suspend_count; + thread->suspend_count = 0; + } else { + thread->suspend_count = + previous_suspend_count - + (suspension_state == ProcessSuspensionState::kSuspended ? 1 : 0); + } memset(&thread->context, 0, sizeof(thread->context)); #if defined(ARCH_CPU_32_BITS) @@ -204,6 +210,7 @@ ProcessReaderWin::Thread::Thread() ProcessReaderWin::ProcessReaderWin() : process_(INVALID_HANDLE_VALUE), process_info_(), + process_memory_(), threads_(), modules_(), suspension_state_(), @@ -220,67 +227,15 @@ bool ProcessReaderWin::Initialize(HANDLE process, process_ = process; suspension_state_ = suspension_state; - process_info_.Initialize(process); + if (!process_info_.Initialize(process)) + return false; + if (!process_memory_.Initialize(process)) + return false; INITIALIZATION_STATE_SET_VALID(initialized_); return true; } -bool ProcessReaderWin::ReadMemory(WinVMAddress at, - WinVMSize num_bytes, - void* into) const { - if (num_bytes == 0) - return 0; - - SIZE_T bytes_read; - if (!ReadProcessMemory(process_, - reinterpret_cast(at), - into, - base::checked_cast(num_bytes), - &bytes_read) || - num_bytes != bytes_read) { - PLOG(ERROR) << "ReadMemory at 0x" << std::hex << at << std::dec << " of " - << num_bytes << " bytes failed"; - return false; - } - return true; -} - -WinVMSize ProcessReaderWin::ReadAvailableMemory(WinVMAddress at, - WinVMSize num_bytes, - void* into) const { - if (num_bytes == 0) - return 0; - - auto ranges = process_info_.GetReadableRanges( - CheckedRange(at, num_bytes)); - - // We only read up until the first unavailable byte, so we only read from the - // first range. If we have no ranges, then no bytes were accessible anywhere - // in the range. - if (ranges.empty()) { - LOG(ERROR) << base::StringPrintf( - "range at 0x%llx, size 0x%llx completely inaccessible", at, num_bytes); - return 0; - } - - // If the start address was adjusted, we couldn't read even the first - // requested byte. - if (ranges.front().base() != at) { - LOG(ERROR) << base::StringPrintf( - "start of range at 0x%llx, size 0x%llx inaccessible", at, num_bytes); - return 0; - } - - DCHECK_LE(ranges.front().size(), num_bytes); - - // If we fail on a normal read, then something went very wrong. - if (!ReadMemory(ranges.front().base(), ranges.front().size(), into)) - return 0; - - return ranges.front().size(); -} - bool ProcessReaderWin::StartTime(timeval* start_time) const { FILETIME creation, exit, kernel, user; if (!GetProcessTimes(process_, &creation, &exit, &kernel, &user)) { @@ -398,7 +353,7 @@ void ProcessReaderWin::ReadThreadData(bool is_64_reading_32) { process_types::NT_TIB tib; thread.teb_address = thread_basic_info.TebBaseAddress; thread.teb_size = sizeof(process_types::TEB); - if (ReadMemory(thread.teb_address, sizeof(tib), &tib)) { + if (process_memory_.Read(thread.teb_address, sizeof(tib), &tib)) { WinVMAddress base = 0; WinVMAddress limit = 0; // If we're reading a WOW64 process, then the TIB we just retrieved is the @@ -409,7 +364,7 @@ void ProcessReaderWin::ReadThreadData(bool is_64_reading_32) { thread.teb_address = tib.Wow64Teb; thread.teb_size = sizeof(process_types::TEB); - if (ReadMemory(thread.teb_address, sizeof(tib32), &tib32)) { + if (process_memory_.Read(thread.teb_address, sizeof(tib32), &tib32)) { base = tib32.StackBase; limit = tib32.StackLimit; } diff --git a/snapshot/win/process_reader_win.h b/snapshot/win/process_reader_win.h index 1794a51a..a4e32aaf 100644 --- a/snapshot/win/process_reader_win.h +++ b/snapshot/win/process_reader_win.h @@ -23,6 +23,7 @@ #include "base/macros.h" #include "build/build_config.h" #include "util/misc/initialization_state_dcheck.h" +#include "util/process/process_memory_win.h" #include "util/win/address_types.h" #include "util/win/process_info.h" @@ -84,25 +85,8 @@ class ProcessReaderWin { //! \return `true` if the target task is a 64-bit process. bool Is64Bit() const { return process_info_.Is64Bit(); } - //! \brief Attempts to read \a num_bytes bytes from the target process - //! starting at address \a at into \a into. - //! - //! \return `true` if the entire region could be read, or `false` with an - //! error logged. - //! - //! \sa ReadAvailableMemory - bool ReadMemory(WinVMAddress at, WinVMSize num_bytes, void* into) const; - - //! \brief Attempts to read \a num_bytes bytes from the target process - //! starting at address \a at into \a into. If some of the specified range - //! is not accessible, reads up to the first inaccessible byte. - //! - //! \return The actual number of bytes read. - //! - //! \sa ReadMemory - WinVMSize ReadAvailableMemory(WinVMAddress at, - WinVMSize num_bytes, - void* into) const; + //! \brief Return a memory reader for the target process. + const ProcessMemoryWin* Memory() const { return &process_memory_; } //! \brief Determines the target process' start time. //! @@ -145,6 +129,7 @@ class ProcessReaderWin { HANDLE process_; ProcessInfo process_info_; + ProcessMemoryWin process_memory_; std::vector threads_; std::vector modules_; ProcessSuspensionState suspension_state_; diff --git a/snapshot/win/process_reader_win_test.cc b/snapshot/win/process_reader_win_test.cc index d0d4d96f..de220081 100644 --- a/snapshot/win/process_reader_win_test.cc +++ b/snapshot/win/process_reader_win_test.cc @@ -17,11 +17,13 @@ #include #include +#include "base/stl_util.h" #include "gtest/gtest.h" #include "test/win/win_multiprocess.h" #include "util/misc/from_pointer_cast.h" #include "util/synchronization/semaphore.h" #include "util/thread/thread.h" +#include "util/win/context_wrappers.h" #include "util/win/scoped_process_suspend.h" namespace crashpad { @@ -42,8 +44,8 @@ TEST(ProcessReaderWin, SelfBasic) { EXPECT_EQ(process_reader.GetProcessInfo().ProcessID(), GetCurrentProcessId()); static constexpr char kTestMemory[] = "Some test memory"; - char buffer[arraysize(kTestMemory)]; - ASSERT_TRUE(process_reader.ReadMemory( + char buffer[base::size(kTestMemory)]; + ASSERT_TRUE(process_reader.Memory()->Read( reinterpret_cast(kTestMemory), sizeof(kTestMemory), &buffer)); EXPECT_STREQ(kTestMemory, buffer); } @@ -72,7 +74,7 @@ class ProcessReaderChild final : public WinMultiprocess { char buffer[sizeof(kTestMemory)]; ASSERT_TRUE( - process_reader.ReadMemory(address, sizeof(kTestMemory), &buffer)); + process_reader.Memory()->Read(address, sizeof(kTestMemory), &buffer)); EXPECT_EQ(strcmp(kTestMemory, buffer), 0); } @@ -106,12 +108,7 @@ TEST(ProcessReaderWin, SelfOneThread) { ASSERT_GE(threads.size(), 1u); EXPECT_EQ(threads[0].id, GetCurrentThreadId()); -#if defined(ARCH_CPU_64_BITS) - EXPECT_NE(threads[0].context.native.Rip, 0u); -#else - EXPECT_NE(threads[0].context.native.Eip, 0u); -#endif - + EXPECT_NE(ProgramCounterFromCONTEXT(&threads[0].context.native), nullptr); EXPECT_EQ(threads[0].suspend_count, 0u); } @@ -133,7 +130,7 @@ class ProcessReaderChildThreadSuspendCount final : public WinMultiprocess { void ThreadMain() override { done_->Wait(); - }; + } private: Semaphore* done_; @@ -188,7 +185,7 @@ class ProcessReaderChildThreadSuspendCount final : public WinMultiprocess { // the pipe. CheckedReadFileAtEOF(ReadPipeHandle()); - for (size_t i = 0; i < arraysize(threads); ++i) + for (size_t i = 0; i < base::size(threads); ++i) done.Signal(); for (auto& thread : threads) thread.Join(); diff --git a/snapshot/win/process_snapshot_win.cc b/snapshot/win/process_snapshot_win.cc index 289f50c0..cafe7b42 100644 --- a/snapshot/win/process_snapshot_win.cc +++ b/snapshot/win/process_snapshot_win.cc @@ -21,8 +21,10 @@ #include #include "base/logging.h" -#include "base/strings/stringprintf.h" +#include "base/numerics/safe_conversions.h" +#include "base/stl_util.h" #include "base/strings/utf_string_conversions.h" +#include "build/build_config.h" #include "util/misc/from_pointer_cast.h" #include "util/misc/time.h" #include "util/win/nt_internals.h" @@ -63,9 +65,9 @@ bool ProcessSnapshotWin::Initialize( if (exception_information_address != 0) { ExceptionInformation exception_information = {}; - if (!process_reader_.ReadMemory(exception_information_address, - sizeof(exception_information), - &exception_information)) { + if (!process_reader_.Memory()->Read(exception_information_address, + sizeof(exception_information), + &exception_information)) { LOG(WARNING) << "ReadMemory ExceptionInformation failed"; return false; } @@ -121,12 +123,12 @@ void ProcessSnapshotWin::GetCrashpadOptions( *options = options_; } -pid_t ProcessSnapshotWin::ProcessID() const { +crashpad::ProcessID ProcessSnapshotWin::ProcessID() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return process_reader_.GetProcessInfo().ProcessID(); } -pid_t ProcessSnapshotWin::ParentProcessID() const { +crashpad::ProcessID ProcessSnapshotWin::ParentProcessID() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return process_reader_.GetProcessInfo().ParentProcessID(); } @@ -232,6 +234,11 @@ std::vector ProcessSnapshotWin::ExtraMemory() const { return extra_memory; } +const ProcessMemory* ProcessSnapshotWin::Memory() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return process_reader_.Memory(); +} + void ProcessSnapshotWin::InitializeThreads( bool gather_indirectly_referenced_memory, uint32_t indirectly_referenced_memory_cap) { @@ -271,7 +278,7 @@ void ProcessSnapshotWin::InitializeUnloadedModules() { // and 32-reading-32, so at the moment, we simply do not retrieve unloaded // modules for 64-reading-32. See https://crashpad.chromium.org/bug/89. -#if defined(ARCH_CPU_X86_64) +#if defined(ARCH_CPU_X86_64) || defined(ARCH_CPU_ARM64) if (!process_reader_.Is64Bit()) { LOG(ERROR) << "reading unloaded modules across bitness not currently supported"; @@ -298,9 +305,9 @@ void ProcessSnapshotWin::InitializeUnloadedModules() { FromPointerCast(event_trace_address); Traits::Pointer pointer_to_array; - if (!process_reader_.ReadMemory(address_in_target_process, - sizeof(pointer_to_array), - &pointer_to_array)) { + if (!process_reader_.Memory()->Read(address_in_target_process, + sizeof(pointer_to_array), + &pointer_to_array)) { LOG(ERROR) << "failed to read target address"; return; } @@ -311,7 +318,7 @@ void ProcessSnapshotWin::InitializeUnloadedModules() { const size_t data_size = *element_size * *element_count; std::vector data(data_size); - if (!process_reader_.ReadMemory(pointer_to_array, data_size, &data[0])) { + if (!process_reader_.Memory()->Read(pointer_to_array, data_size, &data[0])) { LOG(ERROR) << "failed to read unloaded module data"; return; } @@ -328,7 +335,7 @@ void ProcessSnapshotWin::InitializeUnloadedModules() { uet.TimeDateStamp, base::UTF16ToUTF8(base::StringPiece16( uet.ImageName, - wcsnlen(uet.ImageName, arraysize(uet.ImageName)))))); + wcsnlen(uet.ImageName, base::size(uet.ImageName)))))); } } } @@ -377,14 +384,15 @@ void ProcessSnapshotWin::InitializePebData( AddMemorySnapshot(peb_address, peb_size, &extra_memory_); process_types::PEB peb_data; - if (!process_reader_.ReadMemory(peb_address, peb_size, &peb_data)) { + if (!process_reader_.Memory()->Read( + peb_address, base::checked_cast(peb_size), &peb_data)) { LOG(ERROR) << "ReadMemory PEB"; return; } process_types::PEB_LDR_DATA peb_ldr_data; AddMemorySnapshot(peb_data.Ldr, sizeof(peb_ldr_data), &extra_memory_); - if (!process_reader_.ReadMemory( + if (!process_reader_.Memory()->Read( peb_data.Ldr, sizeof(peb_ldr_data), &peb_ldr_data)) { LOG(ERROR) << "ReadMemory PEB_LDR_DATA"; } else { @@ -406,9 +414,9 @@ void ProcessSnapshotWin::InitializePebData( } process_types::RTL_USER_PROCESS_PARAMETERS process_parameters; - if (!process_reader_.ReadMemory(peb_data.ProcessParameters, - sizeof(process_parameters), - &process_parameters)) { + if (!process_reader_.Memory()->Read(peb_data.ProcessParameters, + sizeof(process_parameters), + &process_parameters)) { LOG(ERROR) << "ReadMemory RTL_USER_PROCESS_PARAMETERS"; return; } @@ -451,7 +459,7 @@ void ProcessSnapshotWin::InitializePebData( void ProcessSnapshotWin::AddMemorySnapshot( WinVMAddress address, WinVMSize size, - std::vector>* into) { + std::vector>* into) { if (size == 0) return; @@ -472,14 +480,14 @@ void ProcessSnapshotWin::AddMemorySnapshot( } } - into->push_back(std::make_unique()); - into->back()->Initialize(&process_reader_, address, size); + into->push_back(std::make_unique()); + into->back()->Initialize(process_reader_.Memory(), address, size); } template void ProcessSnapshotWin::AddMemorySnapshotForUNICODE_STRING( const process_types::UNICODE_STRING& us, - std::vector>* into) { + std::vector>* into) { AddMemorySnapshot(us.Buffer, us.Length, into); } @@ -487,7 +495,7 @@ template void ProcessSnapshotWin::AddMemorySnapshotForLdrLIST_ENTRY( const process_types::LIST_ENTRY& le, size_t offset_of_member, - std::vector>* into) { + std::vector>* into) { // Walk the doubly-linked list of entries, adding the list memory itself, as // well as pointed-to strings. typename Traits::Pointer last = le.Blink; @@ -496,7 +504,7 @@ void ProcessSnapshotWin::AddMemorySnapshotForLdrLIST_ENTRY( for (;;) { // |cur| is the pointer to LIST_ENTRY embedded in the LDR_DATA_TABLE_ENTRY. // So we need to offset back to the beginning of the structure. - if (!process_reader_.ReadMemory( + if (!process_reader_.Memory()->Read( cur - offset_of_member, sizeof(entry), &entry)) { return; } @@ -520,16 +528,16 @@ WinVMSize ProcessSnapshotWin::DetermineSizeOfEnvironmentBlock( // be more than enough. std::wstring env_block; env_block.resize(32768); - WinVMSize bytes_read = process_reader_.ReadAvailableMemory( + size_t bytes_read = process_reader_.Memory()->ReadAvailableMemory( start_of_environment_block, env_block.size() * sizeof(env_block[0]), &env_block[0]); env_block.resize( static_cast(bytes_read / sizeof(env_block[0]))); static constexpr wchar_t terminator[] = {0, 0}; - size_t at = env_block.find(std::wstring(terminator, arraysize(terminator))); + size_t at = env_block.find(std::wstring(terminator, base::size(terminator))); if (at != std::wstring::npos) - env_block.resize(at + arraysize(terminator)); + env_block.resize(at + base::size(terminator)); return env_block.size() * sizeof(env_block[0]); } @@ -537,13 +545,13 @@ WinVMSize ProcessSnapshotWin::DetermineSizeOfEnvironmentBlock( template void ProcessSnapshotWin::ReadLock( WinVMAddress start, - std::vector>* into) { + std::vector>* into) { // We're walking the RTL_CRITICAL_SECTION_DEBUG ProcessLocksList, but starting // from an actual RTL_CRITICAL_SECTION, so start by getting to the first // RTL_CRITICAL_SECTION_DEBUG. process_types::RTL_CRITICAL_SECTION critical_section; - if (!process_reader_.ReadMemory( + if (!process_reader_.Memory()->Read( start, sizeof(critical_section), &critical_section)) { LOG(ERROR) << "failed to read RTL_CRITICAL_SECTION"; return; diff --git a/snapshot/win/process_snapshot_win.h b/snapshot/win/process_snapshot_win.h index 9250c13c..8b0bf526 100644 --- a/snapshot/win/process_snapshot_win.h +++ b/snapshot/win/process_snapshot_win.h @@ -31,6 +31,7 @@ #include "snapshot/exception_snapshot.h" #include "snapshot/memory_map_region_snapshot.h" #include "snapshot/memory_snapshot.h" +#include "snapshot/memory_snapshot_generic.h" #include "snapshot/module_snapshot.h" #include "snapshot/process_snapshot.h" #include "snapshot/system_snapshot.h" @@ -38,12 +39,12 @@ #include "snapshot/unloaded_module_snapshot.h" #include "snapshot/win/exception_snapshot_win.h" #include "snapshot/win/memory_map_region_snapshot_win.h" -#include "snapshot/win/memory_snapshot_win.h" #include "snapshot/win/module_snapshot_win.h" #include "snapshot/win/system_snapshot_win.h" #include "snapshot/win/thread_snapshot_win.h" #include "util/misc/initialization_state_dcheck.h" #include "util/misc/uuid.h" +#include "util/process/process_id.h" #include "util/win/address_types.h" #include "util/win/process_structs.h" @@ -112,8 +113,8 @@ class ProcessSnapshotWin final : public ProcessSnapshot { // ProcessSnapshot: - pid_t ProcessID() const override; - pid_t ParentProcessID() const override; + crashpad::ProcessID ProcessID() const override; + crashpad::ProcessID ParentProcessID() const override; void SnapshotTime(timeval* snapshot_time) const override; void ProcessStartTime(timeval* start_time) const override; void ProcessCPUTimes(timeval* user_time, timeval* system_time) const override; @@ -129,6 +130,7 @@ class ProcessSnapshotWin final : public ProcessSnapshot { std::vector MemoryMap() const override; std::vector Handles() const override; std::vector ExtraMemory() const override; + const ProcessMemory* Memory() const override; private: // Initializes threads_ on behalf of Initialize(). @@ -152,18 +154,18 @@ class ProcessSnapshotWin final : public ProcessSnapshot { void AddMemorySnapshot( WinVMAddress address, WinVMSize size, - std::vector>* into); + std::vector>* into); template void AddMemorySnapshotForUNICODE_STRING( const process_types::UNICODE_STRING& us, - std::vector>* into); + std::vector>* into); template void AddMemorySnapshotForLdrLIST_ENTRY( const process_types::LIST_ENTRY& le, size_t offset_of_member, - std::vector>* into); + std::vector>* into); WinVMSize DetermineSizeOfEnvironmentBlock( WinVMAddress start_of_environment_block); @@ -173,10 +175,10 @@ class ProcessSnapshotWin final : public ProcessSnapshot { template void ReadLock( WinVMAddress start, - std::vector>* into); + std::vector>* into); internal::SystemSnapshotWin system_; - std::vector> extra_memory_; + std::vector> extra_memory_; std::vector> threads_; std::vector> modules_; std::vector unloaded_modules_; diff --git a/snapshot/win/process_snapshot_win_test.cc b/snapshot/win/process_snapshot_win_test.cc index 8a3e6a49..7192aa85 100644 --- a/snapshot/win/process_snapshot_win_test.cc +++ b/snapshot/win/process_snapshot_win_test.cc @@ -20,7 +20,6 @@ #include "snapshot/win/pe_image_reader.h" #include "snapshot/win/process_reader_win.h" #include "test/errors.h" -#include "test/gtest_disabled.h" #include "test/test_paths.h" #include "test/win/child_launcher.h" #include "util/file/file_io.h" @@ -120,7 +119,7 @@ TEST(ProcessSnapshotTest, CrashpadInfoChild) { #if defined(ARCH_CPU_64_BITS) TEST(ProcessSnapshotTest, CrashpadInfoChildWOW64) { if (!TestPaths::Has32BitBuildArtifacts()) { - DISABLED_TEST(); + GTEST_SKIP(); } TestImageReaderChild(TestPaths::Architecture::k32Bit); diff --git a/snapshot/win/process_subrange_reader.cc b/snapshot/win/process_subrange_reader.cc index 996fbdf3..124c5d02 100644 --- a/snapshot/win/process_subrange_reader.cc +++ b/snapshot/win/process_subrange_reader.cc @@ -15,6 +15,7 @@ #include "snapshot/win/process_subrange_reader.h" #include "base/logging.h" +#include "base/numerics/safe_conversions.h" #include "snapshot/win/process_reader_win.h" namespace crashpad { @@ -82,7 +83,8 @@ bool ProcessSubrangeReader::ReadMemory(WinVMAddress address, return false; } - return process_reader_->ReadMemory(address, size, into); + return process_reader_->Memory()->Read( + address, base::checked_cast(size), into); } bool ProcessSubrangeReader::InitializeInternal(ProcessReaderWin* process_reader, diff --git a/snapshot/win/system_snapshot_win.cc b/snapshot/win/system_snapshot_win.cc index 74f34a7e..f07c4f67 100644 --- a/snapshot/win/system_snapshot_win.cc +++ b/snapshot/win/system_snapshot_win.cc @@ -26,7 +26,9 @@ #include "base/numerics/safe_conversions.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" +#include "build/build_config.h" #include "util/win/module_version.h" +#include "util/win/scoped_registry_key.h" namespace crashpad { @@ -75,11 +77,9 @@ SystemSnapshotWin::SystemSnapshotWin() os_version_minor_(0), os_version_bugfix_(0), os_server_(false), - initialized_() { -} + initialized_() {} -SystemSnapshotWin::~SystemSnapshotWin() { -} +SystemSnapshotWin::~SystemSnapshotWin() {} void SystemSnapshotWin::Initialize(ProcessReaderWin* process_reader) { INITIALIZATION_STATE_SET_INITIALIZING(initialized_); @@ -125,13 +125,20 @@ void SystemSnapshotWin::Initialize(ProcessReaderWin* process_reader) { CPUArchitecture SystemSnapshotWin::GetCPUArchitecture() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); +#if defined(ARCH_CPU_X86_FAMILY) return process_reader_->Is64Bit() ? kCPUArchitectureX86_64 : kCPUArchitectureX86; +#elif defined(ARCH_CPU_ARM64) + return kCPUArchitectureARM64; +#else +#error Unsupported Windows Arch +#endif } uint32_t SystemSnapshotWin::CPURevision() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); +#if defined(ARCH_CPU_X86_FAMILY) uint32_t raw = CPUX86Signature(); uint8_t stepping = raw & 0xf; uint8_t model = (raw & 0xf0) >> 4; @@ -149,6 +156,14 @@ uint32_t SystemSnapshotWin::CPURevision() const { uint16_t adjusted_family = family + extended_family; uint8_t adjusted_model = model + (extended_model << 4); return (adjusted_family << 16) | (adjusted_model << 8) | stepping; +#elif defined(ARCH_CPU_ARM64) + SYSTEM_INFO system_info; + GetSystemInfo(&system_info); + + return system_info.wProcessorRevision; +#else +#error Unsupported Windows Arch +#endif } uint8_t SystemSnapshotWin::CPUCount() const { @@ -166,6 +181,7 @@ uint8_t SystemSnapshotWin::CPUCount() const { std::string SystemSnapshotWin::CPUVendor() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); +#if defined(ARCH_CPU_X86_FAMILY) int cpu_info[4]; __cpuid(cpu_info, 0); char vendor[12]; @@ -173,6 +189,42 @@ std::string SystemSnapshotWin::CPUVendor() const { *reinterpret_cast(vendor + 4) = cpu_info[3]; *reinterpret_cast(vendor + 8) = cpu_info[2]; return std::string(vendor, sizeof(vendor)); +#elif defined(ARCH_CPU_ARM64) + HKEY key; + + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, + L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", + 0, + KEY_QUERY_VALUE, + &key) != ERROR_SUCCESS) { + return std::string(); + } + + crashpad::ScopedRegistryKey scoped_key(key); + DWORD type; + wchar_t vendor_identifier[1024]; + DWORD vendor_identifier_size = sizeof(vendor_identifier); + + if (RegQueryValueEx(key, + L"VendorIdentifier", + nullptr, + &type, + reinterpret_cast(vendor_identifier), + &vendor_identifier_size) != ERROR_SUCCESS || + type != REG_SZ) { + return std::string(); + } + + std::string return_value; + DCHECK_EQ(vendor_identifier_size % sizeof(wchar_t), 0u); + base::UTF16ToUTF8(vendor_identifier, + vendor_identifier_size / sizeof(wchar_t), + &return_value); + + return return_value.c_str(); +#else +#error Unsupported Windows Arch +#endif } void SystemSnapshotWin::CPUFrequency(uint64_t* current_hz, @@ -212,36 +264,52 @@ void SystemSnapshotWin::CPUFrequency(uint64_t* current_hz, uint32_t SystemSnapshotWin::CPUX86Signature() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); +#if defined(ARCH_CPU_X86_FAMILY) int cpu_info[4]; // We will never run on any processors that don't support at least function 1. __cpuid(cpu_info, 1); return cpu_info[0]; +#else + NOTREACHED(); + return 0; +#endif } uint64_t SystemSnapshotWin::CPUX86Features() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); +#if defined(ARCH_CPU_X86_FAMILY) int cpu_info[4]; // We will never run on any processors that don't support at least function 1. __cpuid(cpu_info, 1); return (static_cast(cpu_info[2]) << 32) | static_cast(cpu_info[3]); +#else + NOTREACHED(); + return 0; +#endif } uint64_t SystemSnapshotWin::CPUX86ExtendedFeatures() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); +#if defined(ARCH_CPU_X86_FAMILY) int cpu_info[4]; // We will never run on any processors that don't support at least extended // function 1. __cpuid(cpu_info, 0x80000001); return (static_cast(cpu_info[2]) << 32) | static_cast(cpu_info[3]); +#else + NOTREACHED(); + return 0; +#endif } uint32_t SystemSnapshotWin::CPUX86Leaf7Features() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); +#if defined(ARCH_CPU_X86_FAMILY) int cpu_info[4]; // Make sure leaf 7 can be called. @@ -251,11 +319,16 @@ uint32_t SystemSnapshotWin::CPUX86Leaf7Features() const { __cpuidex(cpu_info, 7, 0); return cpu_info[1]; +#else + NOTREACHED(); + return 0; +#endif } bool SystemSnapshotWin::CPUX86SupportsDAZ() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); +#if defined(ARCH_CPU_X86_FAMILY) // The correct way to check for denormals-as-zeros (DAZ) support is to examine // mxcsr mask, which can be done with fxsave. See Intel Software Developer's // Manual, Volume 1: Basic Architecture (253665-051), 11.6.3 "Checking for the @@ -277,6 +350,10 @@ bool SystemSnapshotWin::CPUX86SupportsDAZ() const { // Test the DAZ bit. return (mxcsr_mask & (1 << 6)) != 0; +#else + NOTREACHED(); + return 0; +#endif } SystemSnapshot::OperatingSystem SystemSnapshotWin::GetOperatingSystem() const { diff --git a/snapshot/win/system_snapshot_win_test.cc b/snapshot/win/system_snapshot_win_test.cc index c87a6a6a..d93c654c 100644 --- a/snapshot/win/system_snapshot_win_test.cc +++ b/snapshot/win/system_snapshot_win_test.cc @@ -60,6 +60,10 @@ TEST_F(SystemSnapshotWinTest, GetCPUArchitecture) { EXPECT_EQ(cpu_architecture, kCPUArchitectureX86); #elif defined(ARCH_CPU_X86_64) EXPECT_EQ(cpu_architecture, kCPUArchitectureX86_64); +#elif defined(ARCH_CPU_ARM64) + EXPECT_EQ(cpu_architecture, kCPUArchitectureARM64); +#else +#error Unsupported Windows Arch #endif } @@ -69,17 +73,22 @@ TEST_F(SystemSnapshotWinTest, CPUCount) { TEST_F(SystemSnapshotWinTest, CPUVendor) { std::string cpu_vendor = system_snapshot().CPUVendor(); - - // There are a variety of other values, but we don't expect to run our tests - // on them. +#if defined(ARCH_CPU_X86_FAMILY) EXPECT_TRUE(cpu_vendor == "GenuineIntel" || cpu_vendor == "AuthenticAMD"); +#elif defined(ARCH_CPU_ARM64) + EXPECT_FALSE(cpu_vendor.empty()); +#else +#error Unsupported Windows Arch +#endif } +#if defined(ARCH_CPU_X86_FAMILY) TEST_F(SystemSnapshotWinTest, CPUX86SupportsDAZ) { // Most SSE2+ machines support Denormals-Are-Zero. This may fail if run on // older machines. EXPECT_TRUE(system_snapshot().CPUX86SupportsDAZ()); } +#endif TEST_F(SystemSnapshotWinTest, GetOperatingSystem) { EXPECT_EQ(system_snapshot().GetOperatingSystem(), diff --git a/snapshot/win/thread_snapshot_win.cc b/snapshot/win/thread_snapshot_win.cc index 3b927f92..02cc63c5 100644 --- a/snapshot/win/thread_snapshot_win.cc +++ b/snapshot/win/thread_snapshot_win.cc @@ -48,22 +48,27 @@ bool ThreadSnapshotWin::Initialize( if (process_reader->GetProcessInfo().LoggingRangeIsFullyReadable( CheckedRange(thread_.stack_region_address, thread_.stack_region_size))) { - stack_.Initialize(process_reader, + stack_.Initialize(process_reader->Memory(), thread_.stack_region_address, thread_.stack_region_size); } else { - stack_.Initialize(process_reader, 0, 0); + stack_.Initialize(process_reader->Memory(), 0, 0); } if (process_reader->GetProcessInfo().LoggingRangeIsFullyReadable( CheckedRange(thread_.teb_address, thread_.teb_size))) { - teb_.Initialize(process_reader, thread_.teb_address, thread_.teb_size); + teb_.Initialize( + process_reader->Memory(), thread_.teb_address, thread_.teb_size); } else { - teb_.Initialize(process_reader, 0, 0); + teb_.Initialize(process_reader->Memory(), 0, 0); } -#if defined(ARCH_CPU_X86_64) +#if defined(ARCH_CPU_X86) + context_.architecture = kCPUArchitectureX86; + context_.x86 = &context_union_.x86; + InitializeX86Context(process_reader_thread.context.native, context_.x86); +#elif defined(ARCH_CPU_X86_64) if (process_reader->Is64Bit()) { context_.architecture = kCPUArchitectureX86_64; context_.x86_64 = &context_union_.x86_64; @@ -73,11 +78,13 @@ bool ThreadSnapshotWin::Initialize( context_.x86 = &context_union_.x86; InitializeX86Context(process_reader_thread.context.wow64, context_.x86); } +#elif defined(ARCH_CPU_ARM64) + context_.architecture = kCPUArchitectureARM64; + context_.arm64 = &context_union_.arm64; + InitializeARM64Context(process_reader_thread.context.native, context_.arm64); #else - context_.architecture = kCPUArchitectureX86; - context_.x86 = &context_union_.x86; - InitializeX86Context(process_reader_thread.context.native, context_.x86); -#endif // ARCH_CPU_X86_64 +#error Unsupported Windows Arch +#endif // ARCH_CPU_X86 CaptureMemoryDelegateWin capture_memory_delegate( process_reader, diff --git a/snapshot/win/thread_snapshot_win.h b/snapshot/win/thread_snapshot_win.h index 273fc7f0..64ec43de 100644 --- a/snapshot/win/thread_snapshot_win.h +++ b/snapshot/win/thread_snapshot_win.h @@ -24,8 +24,8 @@ #include "build/build_config.h" #include "snapshot/cpu_context.h" #include "snapshot/memory_snapshot.h" +#include "snapshot/memory_snapshot_generic.h" #include "snapshot/thread_snapshot.h" -#include "snapshot/win/memory_snapshot_win.h" #include "snapshot/win/process_reader_win.h" #include "util/misc/initialization_state_dcheck.h" @@ -71,18 +71,22 @@ class ThreadSnapshotWin 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_ARM64) + CPUContextARM64 arm64; +#else +#error Unsupported Windows Arch #endif + } context_union_; CPUContext context_; - MemorySnapshotWin stack_; - MemorySnapshotWin teb_; + MemorySnapshotGeneric stack_; + MemorySnapshotGeneric teb_; ProcessReaderWin::Thread thread_; InitializationStateDcheck initialized_; - std::vector> pointed_to_memory_; + std::vector> pointed_to_memory_; DISALLOW_COPY_AND_ASSIGN(ThreadSnapshotWin); }; diff --git a/test/BUILD.gn b/test/BUILD.gn index d1e6297e..047c786c 100644 --- a/test/BUILD.gn +++ b/test/BUILD.gn @@ -25,8 +25,6 @@ static_library("test") { "filesystem.cc", "filesystem.h", "gtest_death.h", - "gtest_disabled.cc", - "gtest_disabled.h", "hex_string.cc", "hex_string.h", "main_arguments.cc", @@ -36,6 +34,7 @@ static_library("test") { "multiprocess_exec.h", "process_type.cc", "process_type.h", + "scoped_guarded_page.h", "scoped_module_handle.cc", "scoped_module_handle.h", "scoped_temp_dir.cc", @@ -45,9 +44,12 @@ static_library("test") { ] if (crashpad_is_posix || crashpad_is_fuchsia) { - sources += [ "scoped_temp_dir_posix.cc" ] + sources += [ + "scoped_guarded_page_posix.cc", + "scoped_temp_dir_posix.cc", + ] - if (!crashpad_is_fuchsia) { + if (!crashpad_is_fuchsia && !crashpad_is_ios) { sources += [ "multiprocess_exec_posix.cc", "multiprocess_posix.cc", @@ -55,19 +57,32 @@ static_library("test") { } } + if (crashpad_is_mac || crashpad_is_ios) { + sources += [ + "mac/mach_errors.cc", + "mac/mach_errors.h", + ] + } + if (crashpad_is_mac) { sources += [ "mac/dyld.cc", "mac/dyld.h", "mac/exception_swallower.cc", "mac/exception_swallower.h", - "mac/mach_errors.cc", - "mac/mach_errors.h", "mac/mach_multiprocess.cc", "mac/mach_multiprocess.h", ] } + if (crashpad_is_ios) { + sources -= [ + "multiprocess.h", + "multiprocess_exec.cc", + "multiprocess_exec.h", + ] + } + if (crashpad_is_linux || crashpad_is_android) { set_sources_assignment_filter([]) sources += [ @@ -81,6 +96,7 @@ static_library("test") { if (crashpad_is_win) { sources += [ "multiprocess_exec_win.cc", + "scoped_guarded_page_win.cc", "scoped_temp_dir_win.cc", "win/child_launcher.cc", "win/child_launcher.h", @@ -101,12 +117,10 @@ static_library("test") { configs += [ "../build:crashpad_is_in_chromium", - "../build:crashpad_is_in_fuchsia" + "../build:crashpad_is_in_fuchsia", ] - data = [ - "test_paths_test_data_root.txt", - ] + data = [ "test_paths_test_data_root.txt" ] deps = [ "../compat", @@ -123,14 +137,30 @@ static_library("test") { ] } + if (crashpad_is_ios) { + deps += [ ":test_bundle_data" ] + } + if (crashpad_is_win) { libs = [ "shell32.lib" ] } - if (crashpad_is_fuchsia && crashpad_is_in_fuchsia) { - deps += [ - "//zircon/public/lib/fdio", - ] + if (crashpad_is_fuchsia) { + public_deps = [ "../third_party/fuchsia" ] + if (crashpad_is_in_fuchsia) { + deps += [ "//zircon/public/lib/fdio" ] + } + } +} + +if (crashpad_is_ios) { + bundle_data("test_bundle_data") { + testonly = true + + sources = [ "test_paths_test_data_root.txt" ] + + outputs = [ "{{bundle_resources_dir}}/crashpad_test_data/" + + "{{source_root_relative_dir}}/{{source_file_part}}" ] } } @@ -141,13 +171,14 @@ source_set("test_test") { "hex_string_test.cc", "main_arguments_test.cc", "multiprocess_exec_test.cc", + "scoped_guarded_page_test.cc", "scoped_temp_dir_test.cc", "test_paths_test.cc", ] # TODO(crbug.com/812974): Remove !crashpad_is_fuchsia when Fuchsia is no # longer treated as a posix platform. - if (crashpad_is_posix && !crashpad_is_fuchsia) { + if (crashpad_is_posix && !crashpad_is_fuchsia && !crashpad_is_ios) { sources += [ "multiprocess_posix_test.cc" ] } @@ -155,6 +186,13 @@ source_set("test_test") { sources += [ "mac/mach_multiprocess_test.cc" ] } + if (crashpad_is_ios) { + sources -= [ + "multiprocess_exec_test.cc", + "scoped_guarded_page_test.cc", + ] + } + if (crashpad_is_win) { sources += [ "win/win_child_process_test.cc", @@ -171,26 +209,24 @@ source_set("test_test") { "../util", ] - data_deps = [ - ":crashpad_test_test_multiprocess_exec_test_child", - ] + data_deps = [ ":crashpad_test_test_multiprocess_exec_test_child" ] + + if (crashpad_is_ios) { + data_deps -= [ ":crashpad_test_test_multiprocess_exec_test_child" ] + } } -crashpad_executable("crashpad_test_test_multiprocess_exec_test_child") { - sources = [ - "multiprocess_exec_test_child.cc", - ] +if (!crashpad_is_ios) { + crashpad_executable("crashpad_test_test_multiprocess_exec_test_child") { + sources = [ "multiprocess_exec_test_child.cc" ] - deps = [ - "../third_party/mini_chromium:base", - ] + deps = [ "../third_party/mini_chromium:base" ] + } } static_library("gmock_main") { testonly = true - sources = [ - "gtest_main.cc", - ] + sources = [ "gtest_main.cc" ] configs += [ "../build:crashpad_is_in_chromium" ] defines = [ "CRASHPAD_TEST_LAUNCHER_GMOCK" ] deps = [ @@ -200,13 +236,17 @@ static_library("gmock_main") { "../third_party/mini_chromium:base", "../third_party/mini_chromium:base_test_support", ] + if (crashpad_is_android) { + deps += [ "../util" ] + } + if (crashpad_is_ios) { + deps += [ "ios:google_test_setup" ] + } } static_library("gtest_main") { testonly = true - sources = [ - "gtest_main.cc", - ] + sources = [ "gtest_main.cc" ] configs += [ "../build:crashpad_is_in_chromium" ] defines = [ "CRASHPAD_TEST_LAUNCHER_GTEST" ] deps = [ @@ -215,4 +255,10 @@ static_library("gtest_main") { "../third_party/mini_chromium:base", "../third_party/mini_chromium:base_test_support", ] + if (crashpad_is_android) { + deps += [ "../util" ] + } + if (crashpad_is_ios) { + deps += [ "ios:google_test_setup" ] + } } diff --git a/test/fuchsia_crashpad_tests.cmx b/test/fuchsia_crashpad_tests.cmx new file mode 100644 index 00000000..d1870982 --- /dev/null +++ b/test/fuchsia_crashpad_tests.cmx @@ -0,0 +1,24 @@ +{ + "facets": { + "fuchsia.test": { + "system-services": [ + "fuchsia.net.NameLookup", + "fuchsia.posix.socket.Provider" + ] + } + }, + "program": { + "binary": "test/crashpad_tests" + }, + "sandbox": { + "features": [ + "isolated-temp", + "deprecated-ambient-replace-as-executable" + ], + "services": [ + "fuchsia.net.NameLookup", + "fuchsia.posix.socket.Provider", + "fuchsia.process.Launcher" + ] + } +} diff --git a/test/gtest_death.h b/test/gtest_death.h index 69f6361e..f205a2f1 100644 --- a/test/gtest_death.h +++ b/test/gtest_death.h @@ -25,7 +25,7 @@ //! \file -#if defined(OS_MACOSX) || DOXYGEN +#if (defined(OS_MACOSX) && !defined(OS_IOS)) || DOXYGEN //! \brief Wraps the gtest `ASSERT_DEATH_IF_SUPPORTED()` macro to make //! assertions about death caused by crashes. @@ -73,14 +73,14 @@ regex); \ } while (false) -#else // OS_MACOSX +#else // OS_MACOSX && !OS_IOS #define ASSERT_DEATH_CRASH(statement, regex) \ ASSERT_DEATH_IF_SUPPORTED(statement, regex) #define EXPECT_DEATH_CRASH(statement, regex) \ EXPECT_DEATH_IF_SUPPORTED(statement, regex) -#endif // OS_MACOSX +#endif // OS_MACOSX && !OS_IOS #if !(!defined(MINI_CHROMIUM_BASE_LOGGING_H_) && \ defined(OFFICIAL_BUILD) && \ diff --git a/test/gtest_disabled.cc b/test/gtest_disabled.cc deleted file mode 100644 index fab6802a..00000000 --- a/test/gtest_disabled.cc +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2017 The Crashpad Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/gtest_disabled.h" - -#include - -#include "base/format_macros.h" -#include "base/logging.h" -#include "base/strings/stringprintf.h" - -namespace crashpad { -namespace test { - -namespace { - -DisabledTestGtestEnvironment* g_instance; - -} // namespace - -// static -DisabledTestGtestEnvironment* DisabledTestGtestEnvironment::Get() { - if (!g_instance) { - g_instance = new DisabledTestGtestEnvironment(); - } - return g_instance; -} - -void DisabledTestGtestEnvironment::DisabledTest() { - const testing::TestInfo* test_info = - testing::UnitTest::GetInstance()->current_test_info(); - std::string disabled_test = base::StringPrintf( - "%s.%s", test_info->test_case_name(), test_info->name()); - - // Show a DISABLED message using a format similar to gtest, along with a hint - // explaining that OK or FAILED will also appear. - printf( - "This test has been disabled dynamically.\n" - "It will appear as both DISABLED and OK or FAILED.\n" - "[ DISABLED ] %s\n", - disabled_test.c_str()); - - disabled_tests_.push_back(disabled_test); -} - -DisabledTestGtestEnvironment::DisabledTestGtestEnvironment() - : testing::Environment(), - disabled_tests_() { - DCHECK(!g_instance); -} - -DisabledTestGtestEnvironment::~DisabledTestGtestEnvironment() { - DCHECK_EQ(this, g_instance); - g_instance = nullptr; -} - -void DisabledTestGtestEnvironment::TearDown() { - if (!disabled_tests_.empty()) { - printf( - "[ DISABLED ] %" PRIuS " dynamically disabled test%s, listed below:\n" - "[ DISABLED ] %s also counted in PASSED or FAILED below.\n", - disabled_tests_.size(), - disabled_tests_.size() == 1 ? "" : "s", - disabled_tests_.size() == 1 ? "This test is" : "These tests are"); - for (const std::string& disabled_test : disabled_tests_) { - printf("[ DISABLED ] %s\n", disabled_test.c_str()); - } - } -} - -} // namespace test -} // namespace crashpad diff --git a/test/gtest_disabled.h b/test/gtest_disabled.h deleted file mode 100644 index 9415cba2..00000000 --- a/test/gtest_disabled.h +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2017 The Crashpad Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef CRASHPAD_TEST_GTEST_DISABLED_H_ -#define CRASHPAD_TEST_GTEST_DISABLED_H_ - -#include -#include - -#include "base/macros.h" -#include "gtest/gtest.h" - -//! \file - -namespace crashpad { -namespace test { - -//! \brief Provides support for dynamically disabled gtest tests. -//! -//! A test runner must register this with gtest as follows prior to calling -//! `RUN_ALL_TESTS()`: -//! \code -//! testing::AddGlobalTestEnvironment( -//! crashpad::test::DisabledTestGtestEnvironment::Get()); -//! \endcode -class DisabledTestGtestEnvironment final : public testing::Environment { - public: - //! \brief Returns the DisabledTestGtestEnvironment singleton instance, - //! creating it if necessary. - static DisabledTestGtestEnvironment* Get(); - - //! \brief Displays a message about a test being disabled, and arranges for - //! this information to be duplicated in TearDown(). - //! - //! This method is for the internal use of the DISABLED_TEST() macro. Do not - //! call it directly, use the macro instead. - void DisabledTest(); - - private: - DisabledTestGtestEnvironment(); - ~DisabledTestGtestEnvironment() override; - - // testing::Environment: - void TearDown() override; - - std::vector disabled_tests_; - - DISALLOW_COPY_AND_ASSIGN(DisabledTestGtestEnvironment); -}; - -} // namespace test -} // namespace crashpad - -//! \brief Displays a message about a test being disabled, and returns early. -//! -//! gtest only provides a mechanism for tests to be disabled statically, by -//! prefixing test case names or test names with `DISABLED_`. When it is -//! necessary to disable tests dynamically, gtest provides no assistance. This -//! macro displays a message about the disabled test and returns early. The -//! dynamically disabled test will also be displayed during gtest global test -//! environment tear-down before the test executable exits. -//! -//! This macro may only be invoked from the context of a gtest test. -//! -//! There’s a long-standing gtest -//! feature request to provide this functionality directly in gtest, but -//! since it hasn’t been implemented, this macro provides a local mechanism to -//! achieve it. -#define DISABLED_TEST() \ - do { \ - ::crashpad::test::DisabledTestGtestEnvironment::Get()->DisabledTest(); \ - return; \ - } while (false) - -#endif // CRASHPAD_TEST_GTEST_DISABLED_H_ diff --git a/test/gtest_main.cc b/test/gtest_main.cc index ebdbeb9d..73cdddfa 100644 --- a/test/gtest_main.cc +++ b/test/gtest_main.cc @@ -14,7 +14,6 @@ #include "build/build_config.h" #include "gtest/gtest.h" -#include "test/gtest_disabled.h" #include "test/main_arguments.h" #include "test/multiprocess_exec.h" @@ -22,6 +21,14 @@ #include "gmock/gmock.h" #endif // CRASHPAD_TEST_LAUNCHER_GMOCK +#if defined(OS_ANDROID) +#include "util/linux/initial_signal_dispositions.h" +#endif // OS_ANDROID + +#if defined(OS_IOS) +#include "test/ios/google_test_setup.h" +#endif + #if defined(OS_WIN) #include "test/win/win_child_process.h" #endif // OS_WIN @@ -34,6 +41,7 @@ namespace { +#if !defined(OS_IOS) bool GetChildTestFunctionName(std::string* child_func_name) { constexpr size_t arg_length = sizeof(crashpad::test::internal::kChildTestFunction) - 1; @@ -46,19 +54,24 @@ bool GetChildTestFunctionName(std::string* child_func_name) { } return false; } +#endif // !OS_IOS } // namespace int main(int argc, char* argv[]) { - crashpad::test::InitializeMainArguments(argc, argv); - testing::AddGlobalTestEnvironment( - crashpad::test::DisabledTestGtestEnvironment::Get()); +#if defined(OS_ANDROID) + crashpad::InitializeSignalDispositions(); +#endif // OS_ANDROID + crashpad::test::InitializeMainArguments(argc, argv); + +#if !defined(OS_IOS) std::string child_func_name; if (GetChildTestFunctionName(&child_func_name)) { return crashpad::test::internal::CheckedInvokeMultiprocessChild( child_func_name); } +#endif // !OS_IOS #if defined(CRASHPAD_IS_IN_CHROMIUM) @@ -68,6 +81,8 @@ int main(int argc, char* argv[]) { // runner. const bool use_chromium_test_launcher = !crashpad::test::WinChildProcess::IsChildProcess(); +#elif defined(OS_ANDROID) + constexpr bool use_chromium_test_launcher = false; #else // OS_WIN constexpr bool use_chromium_test_launcher = true; #endif // OS_WIN @@ -79,7 +94,7 @@ int main(int argc, char* argv[]) { return base::LaunchUnitTests( argc, argv, - base::Bind(&base::TestSuite::Run, base::Unretained(&test_suite))); + base::BindOnce(&base::TestSuite::Run, base::Unretained(&test_suite))); } #endif // CRASHPAD_IS_IN_CHROMIUM @@ -92,5 +107,12 @@ int main(int argc, char* argv[]) { #error #define CRASHPAD_TEST_LAUNCHER_GTEST or CRASHPAD_TEST_LAUNCHER_GMOCK #endif // CRASHPAD_TEST_LAUNCHER_GMOCK +#if defined(OS_IOS) + // iOS needs to run tests within the context of an app, so call a helper that + // invokes UIApplicationMain(). The application delegate will call + // RUN_ALL_TESTS() and exit before returning control to this function. + crashpad::test::IOSLaunchApplicationAndRunTests(argc, argv); +#else return RUN_ALL_TESTS(); +#endif } diff --git a/test/hex_string.h b/test/hex_string.h index 435a692e..b0d44531 100644 --- a/test/hex_string.h +++ b/test/hex_string.h @@ -29,8 +29,8 @@ namespace test { //! uint8_t expected[10]; //! uint8_t observed[10]; //! // … -//! EXPECT_EQ(BytesToHexString(observed, arraysize(observed)), -//! BytesToHexString(expected, arraysize(expected))); +//! EXPECT_EQ(BytesToHexString(observed, base::size(observed)), +//! BytesToHexString(expected, base::size(expected))); //! \endcode std::string BytesToHexString(const void* bytes, size_t length); diff --git a/test/hex_string_test.cc b/test/hex_string_test.cc index 68745e6f..3a09eb76 100644 --- a/test/hex_string_test.cc +++ b/test/hex_string_test.cc @@ -14,7 +14,7 @@ #include "test/hex_string.h" -#include "base/macros.h" +#include "base/stl_util.h" #include "gtest/gtest.h" namespace crashpad { @@ -25,7 +25,7 @@ TEST(HexString, HexString) { EXPECT_EQ(BytesToHexString(nullptr, 0), ""); static constexpr char kBytes[] = "Abc123xyz \x0a\x7f\xf0\x9f\x92\xa9_"; - EXPECT_EQ(BytesToHexString(kBytes, arraysize(kBytes)), + EXPECT_EQ(BytesToHexString(kBytes, base::size(kBytes)), "41626331323378797a200a7ff09f92a95f00"); } diff --git a/test/ios/BUILD.gn b/test/ios/BUILD.gn new file mode 100644 index 00000000..d548a279 --- /dev/null +++ b/test/ios/BUILD.gn @@ -0,0 +1,80 @@ +# Copyright 2020 The Crashpad Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("../../build/crashpad_buildconfig.gni") + +if (crashpad_is_in_chromium) { + import("//build/config/ios/rules.gni") +} else if (crashpad_is_standalone) { + import("//third_party/mini_chromium/mini_chromium/build/ios/rules.gni") +} + +group("all_tests") { + testonly = true + deps = [ + ":ios_crash_xcuitests_module", + "host:ios_crash_xcuitests", + ] +} + +source_set("google_test_runner_shared_headers") { + testonly = true + sources = [ "cptest_google_test_runner_delegate.h" ] +} + +source_set("google_test_runner") { + testonly = true + sources = [ "cptest_google_test_runner.mm" ] + configs += [ "../..:crashpad_config" ] + deps = [ + "../../build:ios_enable_arc", + "../../build:ios_xctest", + "../../test/ios:google_test_runner_shared_headers", + "../../third_party/mini_chromium:base", + ] + libs = [ "UIKit.framework" ] +} + +source_set("google_test_setup") { + testonly = true + sources = [ + "google_test_setup.h", + "google_test_setup.mm", + ] + configs += [ "../..:crashpad_config" ] + deps = [ + ":google_test_runner_shared_headers", + "../../build:ios_enable_arc", + "../../third_party/gtest:gtest", + "../../third_party/mini_chromium:base", + ] + libs = [ "UIKit.framework" ] +} + +source_set("xcuitests") { + testonly = true + sources = [ "crash_type_xctest.mm" ] + configs += [ "../..:crashpad_config" ] + deps = [ + "../../build:ios_enable_arc", + "../../build:ios_xctest", + "../../test/ios/host:app_shared_sources", + "../../third_party/edo", + ] +} + +ios_xcuitest_test("ios_crash_xcuitests_module") { + xcode_test_application_name = "ios_crash_xcuitests" + deps = [ ":xcuitests" ] +} diff --git a/test/ios/cptest_google_test_runner.mm b/test/ios/cptest_google_test_runner.mm new file mode 100644 index 00000000..ae933fcc --- /dev/null +++ b/test/ios/cptest_google_test_runner.mm @@ -0,0 +1,41 @@ +// Copyright 2020 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import +#import + +#include "base/logging.h" +#import "test/ios/cptest_google_test_runner_delegate.h" + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +@interface CPTestGoogleTestRunner : XCTestCase +@end + +@implementation CPTestGoogleTestRunner + +- (void)testRunGoogleTests { + id appDelegate = UIApplication.sharedApplication.delegate; + DCHECK([appDelegate + conformsToProtocol:@protocol(CPTestGoogleTestRunnerDelegate)]); + + id runnerDelegate = + static_cast>(appDelegate); + DCHECK(runnerDelegate.supportsRunningGoogleTestsWithXCTest); + XCTAssertTrue([runnerDelegate runGoogleTests] == 0); +} + +@end diff --git a/test/ios/cptest_google_test_runner_delegate.h b/test/ios/cptest_google_test_runner_delegate.h new file mode 100644 index 00000000..f88d63de --- /dev/null +++ b/test/ios/cptest_google_test_runner_delegate.h @@ -0,0 +1,30 @@ +// Copyright 2020 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_TEST_IOS_CPTEST_GOOGLE_TEST_RUNNER_DELEGATE_ +#define CRASHPAD_TEST_IOS_CPTEST_GOOGLE_TEST_RUNNER_DELEGATE_ + +@protocol CPTestGoogleTestRunnerDelegate + +// Returns YES if this delegate supports running GoogleTests via a call to +// |runGoogleTests|. +@property(nonatomic, readonly, assign) + BOOL supportsRunningGoogleTestsWithXCTest; + +// Runs GoogleTests and returns the final exit code. +- (int)runGoogleTests; + +@end + +#endif // CRASHPAD_TEST_IOS_CPTEST_GOOGLE_TEST_RUNNER_DELEGATE_H_ diff --git a/test/ios/crash_type_xctest.mm b/test/ios/crash_type_xctest.mm new file mode 100644 index 00000000..45291243 --- /dev/null +++ b/test/ios/crash_type_xctest.mm @@ -0,0 +1,241 @@ +// Copyright 2020 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +#include +#import "Service/Sources/EDOClientService.h" +#import "test/ios/host/cptest_shared_object.h" + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +@interface CPTestTestCase : XCTestCase { + XCUIApplication* _app; +} + +@end + +@implementation CPTestTestCase + +- (void)handleCrashUnderSymbol:(id)arg1 { + // For now, do nothing. In the future this can be something testable. +} + ++ (void)setUp { + // Swizzle away the handleCrashUnderSymbol callback. Without this, any time + // the host app is intentionally crashed, the test is immediately failed. + SEL originalSelector = NSSelectorFromString(@"handleCrashUnderSymbol:"); + SEL swizzledSelector = @selector(handleCrashUnderSymbol:); + + Method originalMethod = class_getInstanceMethod( + objc_getClass("XCUIApplicationImpl"), originalSelector); + Method swizzledMethod = + class_getInstanceMethod([self class], swizzledSelector); + + method_exchangeImplementations(originalMethod, swizzledMethod); + + // Override EDO default error handler. Without this, the default EDO error + // handler will throw an error and fail the test. + [EDOClientService setErrorHandler:^(NSError* error){ + // Do nothing. + }]; +} + +- (void)setUp { + _app = [[XCUIApplication alloc] init]; + [_app launch]; +} + +- (void)testEDO { + CPTestSharedObject* rootObject = [EDOClientService rootObjectWithPort:12345]; + NSString* result = [rootObject testEDO]; + XCTAssertEqualObjects(result, @"crashpad"); +} + +- (void)testSegv { + XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground); + + // Crash the app. + CPTestSharedObject* rootObject = [EDOClientService rootObjectWithPort:12345]; + [rootObject crashSegv]; + + // Confirm the app is not running. + XCTAssertTrue([_app waitForState:XCUIApplicationStateNotRunning timeout:15]); + XCTAssertTrue(_app.state == XCUIApplicationStateNotRunning); + + // TODO: Query the app for crash data + [_app launch]; + XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground); +} + +- (void)testKillAbort { + XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground); + + // Crash the app. + CPTestSharedObject* rootObject = [EDOClientService rootObjectWithPort:12345]; + [rootObject crashKillAbort]; + + // Confirm the app is not running. + XCTAssertTrue([_app waitForState:XCUIApplicationStateNotRunning timeout:15]); + XCTAssertTrue(_app.state == XCUIApplicationStateNotRunning); + + // TODO: Query the app for crash data + [_app launch]; + XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground); +} + +- (void)testTrap { + XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground); + + // Crash the app. + CPTestSharedObject* rootObject = [EDOClientService rootObjectWithPort:12345]; + [rootObject crashTrap]; + + // Confirm the app is not running. + XCTAssertTrue([_app waitForState:XCUIApplicationStateNotRunning timeout:15]); + XCTAssertTrue(_app.state == XCUIApplicationStateNotRunning); + + // TODO: Query the app for crash data + [_app launch]; + XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground); +} + +- (void)testAbort { + XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground); + + // Crash the app. + CPTestSharedObject* rootObject = [EDOClientService rootObjectWithPort:12345]; + [rootObject crashAbort]; + + // Confirm the app is not running. + XCTAssertTrue([_app waitForState:XCUIApplicationStateNotRunning timeout:15]); + XCTAssertTrue(_app.state == XCUIApplicationStateNotRunning); + + // TODO: Query the app for crash data + [_app launch]; + XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground); +} + +- (void)testBadAccess { + XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground); + + // Crash the app. + CPTestSharedObject* rootObject = [EDOClientService rootObjectWithPort:12345]; + [rootObject crashBadAccess]; + + // Confirm the app is not running. + XCTAssertTrue([_app waitForState:XCUIApplicationStateNotRunning timeout:15]); + XCTAssertTrue(_app.state == XCUIApplicationStateNotRunning); + + // TODO: Query the app for crash data + [_app launch]; + XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground); +} + +- (void)testException { + XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground); + + // Crash the app. + CPTestSharedObject* rootObject = [EDOClientService rootObjectWithPort:12345]; + [rootObject crashException]; + + // Confirm the app is not running. + XCTAssertTrue([_app waitForState:XCUIApplicationStateNotRunning timeout:15]); + XCTAssertTrue(_app.state == XCUIApplicationStateNotRunning); + + // TODO: Query the app for crash data + [_app launch]; + XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground); +} + +- (void)testNSException { + XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground); + + // Crash the app. + CPTestSharedObject* rootObject = [EDOClientService rootObjectWithPort:12345]; + [rootObject crashNSException]; + + // Confirm the app is not running. + XCTAssertTrue([_app waitForState:XCUIApplicationStateNotRunning timeout:15]); + XCTAssertTrue(_app.state == XCUIApplicationStateNotRunning); + + // TODO: Query the app for crash data + [_app launch]; + XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground); +} + +- (void)testCrashUnreocgnizedSelectorAfterDelay { + XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground); + + // Crash the app. + CPTestSharedObject* rootObject = [EDOClientService rootObjectWithPort:12345]; + [rootObject crashUnreocgnizedSelectorAfterDelay]; + + // Confirm the app is not running. + XCTAssertTrue([_app waitForState:XCUIApplicationStateNotRunning timeout:15]); + XCTAssertTrue(_app.state == XCUIApplicationStateNotRunning); + + // TODO: Query the app for crash data + [_app launch]; + XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground); +} + +- (void)testCatchUIGestureEnvironmentNSException { + XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground); + + // Tap the button with the string UIGestureEnvironmentException. + [_app.buttons[@"UIGestureEnvironmentException"] tap]; + + // Confirm the app is not running. + XCTAssertTrue([_app waitForState:XCUIApplicationStateNotRunning timeout:15]); + XCTAssertTrue(_app.state == XCUIApplicationStateNotRunning); + + // TODO: Query the app for crash data + [_app launch]; + XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground); +} + +- (void)testCatchNSException { + XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground); + + // The app should not crash + CPTestSharedObject* rootObject = [EDOClientService rootObjectWithPort:12345]; + [rootObject catchNSException]; + + XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground); +} + +- (void)testRecursion { + // TODO(justincohen): Crashpad iOS does not currently support stack type + // crashes. + return; + + XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground); + + // Crash the app. + CPTestSharedObject* rootObject = [EDOClientService rootObjectWithPort:12345]; + [rootObject crashRecursion]; + + // Confirm the app is not running. + XCTAssertTrue([_app waitForState:XCUIApplicationStateNotRunning timeout:15]); + XCTAssertTrue(_app.state == XCUIApplicationStateNotRunning); + + // TODO: Query the app for crash data + [_app launch]; + XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground); +} + +@end diff --git a/test/ios/google_test_setup.h b/test/ios/google_test_setup.h new file mode 100644 index 00000000..98bb9427 --- /dev/null +++ b/test/ios/google_test_setup.h @@ -0,0 +1,33 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_TEST_IOS_GOOGLE_TEST_SETUP_ +#define CRASHPAD_TEST_IOS_GOOGLE_TEST_SETUP_ + +namespace crashpad { +namespace test { + +//! \brief Runs all registered tests in the context of a UIKit application. +//! +//! Invokes UIApplicationMain() to launch the iOS application and runs all +//! registered tests after the application finishes +//! launching. UIApplicationMain() brings up the main runloop and never returns, +//! so therefore this function never returns either. It invokes _exit() to +//! terminate the application after tests have completed. +void IOSLaunchApplicationAndRunTests(int argc, char* argv[]); + +} // namespace test +} // namespace crashpad + +#endif // CRASHPAD_TEST_IOS_GOOGLE_TEST_SETUP_ diff --git a/test/ios/google_test_setup.mm b/test/ios/google_test_setup.mm new file mode 100644 index 00000000..109c4b68 --- /dev/null +++ b/test/ios/google_test_setup.mm @@ -0,0 +1,134 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "test/ios/google_test_setup.h" + +#import + +#include "base/logging.h" +#include "gtest/gtest.h" +#include "test/ios/cptest_google_test_runner_delegate.h" + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +@interface UIApplication (Testing) +- (void)_terminateWithStatus:(int)status; +@end + +namespace { + +// The iOS watchdog timer will kill an app that doesn't spin the main event +// loop often enough. This uses a Gtest TestEventListener to spin the current +// loop after each test finishes. However, if any individual test takes too +// long, it is still possible that the app will get killed. +class IOSRunLoopListener : public testing::EmptyTestEventListener { + public: + virtual void OnTestEnd(const testing::TestInfo& test_info) { + @autoreleasepool { + // At the end of the test, spin the default loop for a moment. + NSDate* stop_date = [NSDate dateWithTimeIntervalSinceNow:0.001]; + [[NSRunLoop currentRunLoop] runUntilDate:stop_date]; + } + } +}; + +void RegisterTestEndListener() { + testing::TestEventListeners& listeners = + testing::UnitTest::GetInstance()->listeners(); + listeners.Append(new IOSRunLoopListener); +} + +} // namespace + +@interface CPTestUnitTestApplicationDelegate + : NSObject +@property(nonatomic, readwrite, strong) UIWindow* window; +- (void)runTests; +@end + +@implementation CPTestUnitTestApplicationDelegate + +- (BOOL)application:(UIApplication*)application + didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { + self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds]; + self.window.backgroundColor = UIColor.whiteColor; + [self.window makeKeyAndVisible]; + + UIViewController* controller = [[UIViewController alloc] init]; + [self.window setRootViewController:controller]; + + // Add a label with the app name. + UILabel* label = [[UILabel alloc] initWithFrame:controller.view.bounds]; + label.text = [[NSProcessInfo processInfo] processName]; + label.textAlignment = NSTextAlignmentCenter; + label.textColor = UIColor.blackColor; + [controller.view addSubview:label]; + + // Queue up the test run. + if (![self supportsRunningGoogleTestsWithXCTest]) { + // When running in XCTest mode, XCTest will invoke |runGoogleTest| directly. + // Otherwise, schedule a call to |runTests|. + [self performSelector:@selector(runTests) withObject:nil afterDelay:0.1]; + } + + return YES; +} + +- (BOOL)supportsRunningGoogleTestsWithXCTest { + return getenv("XCTestConfigurationFilePath") != nullptr; +} + +- (int)runGoogleTests { + RegisterTestEndListener(); + int exitStatus = RUN_ALL_TESTS(); + return exitStatus; +} + +- (void)runTests { + DCHECK(![self supportsRunningGoogleTestsWithXCTest]); + + int exitStatus = [self runGoogleTests]; + + // If a test app is too fast, it will exit before Instruments has has a + // a chance to initialize and no test results will be seen. + // TODO(crbug.com/137010): Figure out how much time is actually needed, and + // sleep only to make sure that much time has elapsed since launch. + [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]]; + self.window = nil; + + // Use the hidden selector to try and cleanly take down the app (otherwise + // things can think the app crashed even on a zero exit status). + UIApplication* application = [UIApplication sharedApplication]; + [application _terminateWithStatus:exitStatus]; + + exit(exitStatus); +} + +@end + +namespace crashpad { +namespace test { + +void IOSLaunchApplicationAndRunTests(int argc, char* argv[]) { + @autoreleasepool { + int exit_status = UIApplicationMain( + argc, argv, nil, @"CPTestUnitTestApplicationDelegate"); + exit(exit_status); + } +} + +} // namespace test +} // namespace crashpad diff --git a/test/ios/host/BUILD.gn b/test/ios/host/BUILD.gn new file mode 100644 index 00000000..cd6995a9 --- /dev/null +++ b/test/ios/host/BUILD.gn @@ -0,0 +1,57 @@ +# Copyright 2020 The Crashpad Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("../../../build/crashpad_buildconfig.gni") + +if (crashpad_is_in_chromium) { + import("//build/config/ios/rules.gni") +} else if (crashpad_is_standalone) { + import("//third_party/mini_chromium/mini_chromium/build/ios/rules.gni") +} + +source_set("app_shared_sources") { + testonly = true + sources = [ "cptest_shared_object.h" ] + configs += [ "../../..:crashpad_config" ] + deps = [ "../../../build:ios_enable_arc" ] + libs = [ "UIKit.framework" ] +} + +static_library("app_host_sources") { + testonly = true + sources = [ + "cptest_application_delegate.h", + "cptest_application_delegate.mm", + "cptest_crash_view_controller.h", + "cptest_crash_view_controller.mm", + "main.mm", + ] + configs += [ "../../..:crashpad_config" ] + deps = [ + ":app_shared_sources", + "../../../build:ios_enable_arc", + "../../../client", + "../../../third_party/edo", + ] + libs = [ + "Foundation.framework", + "UIKit.framework", + ] +} + +ios_app_bundle("ios_crash_xcuitests") { + info_plist = "Info.plist" + testonly = true + deps = [ ":app_host_sources" ] +} diff --git a/test/ios/host/Info.plist b/test/ios/host/Info.plist new file mode 100644 index 00000000..8944a2e4 --- /dev/null +++ b/test/ios/host/Info.plist @@ -0,0 +1,112 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + ${PRODUCT_NAME} + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + ${IOS_BUNDLE_ID_PREFIX}.gtest.${EXECUTABLE_NAME:rfc1034identifier} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + LSRequiresIPhoneOS + + UILaunchImages + + + UILaunchImageMinimumOSVersion + 9.0 + UILaunchImageName + Default + UILaunchImageSize + {320, 480} + + + UILaunchImageMinimumOSVersion + 9.0 + UILaunchImageName + Default + UILaunchImageSize + {320, 568} + + + UILaunchImageMinimumOSVersion + 9.0 + UILaunchImageName + Default + UILaunchImageSize + {375, 667} + + + UILaunchImageMinimumOSVersion + 9.0 + UILaunchImageName + Default + UILaunchImageSize + {414, 736} + + + UILaunchImageMinimumOSVersion + 9.0 + UILaunchImageName + Default + UILaunchImageSize + {375, 812} + + + UILaunchImageMinimumOSVersion + 9.0 + UILaunchImageName + Default + UILaunchImageSize + {414, 896} + + + UILaunchImages~ipad + + + UILaunchImageMinimumOSVersion + 9.0 + UILaunchImageName + Default + UILaunchImageSize + {768, 1024} + + + UILaunchImageMinimumOSVersion + 9.0 + UILaunchImageName + Default + UILaunchImageSize + {1024, 1366} + + + UILaunchImageMinimumOSVersion + 9.0 + UILaunchImageName + Default + UILaunchImageSize + {832, 1114} + + + UISupportedInterfaceOrientation + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/test/ios/host/cptest_application_delegate.h b/test/ios/host/cptest_application_delegate.h new file mode 100644 index 00000000..ca835f12 --- /dev/null +++ b/test/ios/host/cptest_application_delegate.h @@ -0,0 +1,23 @@ +// Copyright 2020 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_TEST_IOS_HOST_CPTEST_APPLICATION_DELEGATE_H_ +#define CRASHPAD_TEST_IOS_HOST_CPTEST_APPLICATION_DELEGATE_H_ + +#import + +@interface CPTestApplicationDelegate : UIResponder +@end + +#endif // CRASHPAD_TEST_IOS_HOST_CPTEST_APPLICATION_DELEGATE_H_ diff --git a/test/ios/host/cptest_application_delegate.mm b/test/ios/host/cptest_application_delegate.mm new file mode 100644 index 00000000..80baf8b3 --- /dev/null +++ b/test/ios/host/cptest_application_delegate.mm @@ -0,0 +1,126 @@ +// Copyright 2020 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "test/ios/host/cptest_application_delegate.h" + +#include +#include +#include +#include +#include + +#include + +#import "Service/Sources/EDOHostNamingService.h" +#import "Service/Sources/EDOHostService.h" +#include "client/crashpad_client.h" +#import "test/ios/host/cptest_crash_view_controller.h" +#import "test/ios/host/cptest_shared_object.h" + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +@implementation CPTestApplicationDelegate + +@synthesize window = _window; + +- (BOOL)application:(UIApplication*)application + didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { + // Start up crashpad. + crashpad::CrashpadClient client; + client.StartCrashpadInProcessHandler(); + + self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + [self.window makeKeyAndVisible]; + self.window.backgroundColor = UIColor.greenColor; + + CPTestCrashViewController* controller = + [[CPTestCrashViewController alloc] init]; + self.window.rootViewController = controller; + + // Start up EDO. + [EDOHostService serviceWithPort:12345 + rootObject:[[CPTestSharedObject alloc] init] + queue:dispatch_get_main_queue()]; + return YES; +} + +@end + +@implementation CPTestSharedObject + +- (NSString*)testEDO { + return @"crashpad"; +} + +- (void)crashBadAccess { + strcpy(nullptr, "bla"); +} + +- (void)crashKillAbort { + kill(getpid(), SIGABRT); +} + +- (void)crashSegv { + long* zero = nullptr; + *zero = 0xc045004d; +} + +- (void)crashTrap { + __builtin_trap(); +} + +- (void)crashAbort { + abort(); +} + +- (void)crashException { + std::vector empty_vector = {}; + empty_vector.at(42); +} + +- (void)crashNSException { + // EDO has its own sinkhole. + dispatch_async(dispatch_get_main_queue(), ^{ + NSArray* empty_array = @[]; + [empty_array objectAtIndex:42]; + }); +} + +- (void)catchNSException { + @try { + NSArray* empty_array = @[]; + [empty_array objectAtIndex:42]; + } @catch (NSException* exception) { + } @finally { + } +} + +- (void)crashUnreocgnizedSelectorAfterDelay { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wundeclared-selector" + [self performSelector:@selector(does_not_exist) withObject:nil afterDelay:1]; +#pragma clang diagnostic pop +} + +- (void)recurse { + [self recurse]; +} + +- (void)crashRecursion { + [self recurse]; +} + +@end diff --git a/test/ios/host/cptest_crash_view_controller.h b/test/ios/host/cptest_crash_view_controller.h new file mode 100644 index 00000000..2a41e129 --- /dev/null +++ b/test/ios/host/cptest_crash_view_controller.h @@ -0,0 +1,23 @@ +// Copyright 2020 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_TEST_IOS_HOST_CPTEST_CRASH_VIEW_CONTROLLER_H_ +#define CRASHPAD_TEST_IOS_HOST_CPTEST_CRASH_VIEW_CONTROLLER_H_ + +#import + +@interface CPTestCrashViewController : UIViewController +@end + +#endif // CRASHPAD_TEST_IOS_HOST_CPTEST_CRASH_VIEW_CONTROLLER_H_ diff --git a/test/ios/host/cptest_crash_view_controller.mm b/test/ios/host/cptest_crash_view_controller.mm new file mode 100644 index 00000000..c4677229 --- /dev/null +++ b/test/ios/host/cptest_crash_view_controller.mm @@ -0,0 +1,65 @@ +// Copyright 2020 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "test/ios/host/cptest_crash_view_controller.h" + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +@implementation CPTestCrashViewController + +- (void)loadView { + self.view = [[UIView alloc] init]; + + UIStackView* buttonStack = [[UIStackView alloc] init]; + buttonStack.axis = UILayoutConstraintAxisVertical; + buttonStack.spacing = 6; + + UIButton* button = [UIButton new]; + [button setTitle:@"UIGestureEnvironmentException" + forState:UIControlStateNormal]; + UITapGestureRecognizer* tapGesture = [[UITapGestureRecognizer alloc] + initWithTarget:self + action:@selector(throwUIGestureEnvironmentException)]; + [button addGestureRecognizer:tapGesture]; + [button setTranslatesAutoresizingMaskIntoConstraints:NO]; + + [buttonStack addArrangedSubview:button]; + + [self.view addSubview:buttonStack]; + + [buttonStack setTranslatesAutoresizingMaskIntoConstraints:NO]; + + [NSLayoutConstraint activateConstraints:@[ + [buttonStack.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor], + [buttonStack.topAnchor constraintEqualToAnchor:self.view.topAnchor], + [buttonStack.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], + [buttonStack.trailingAnchor + constraintEqualToAnchor:self.view.trailingAnchor], + ]]; +} + +- (void)throwUIGestureEnvironmentException { + NSArray* empty_array = @[]; + [empty_array objectAtIndex:42]; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.view.backgroundColor = UIColor.redColor; +} + +@end diff --git a/test/ios/host/cptest_shared_object.h b/test/ios/host/cptest_shared_object.h new file mode 100644 index 00000000..70c814b0 --- /dev/null +++ b/test/ios/host/cptest_shared_object.h @@ -0,0 +1,55 @@ +// Copyright 2020 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_TEST_IOS_HOST_SHARED_OBJECT_H_ +#define CRASHPAD_TEST_IOS_HOST_SHARED_OBJECT_H_ + +#import + +@interface CPTestSharedObject : NSObject +// Returns the string "crashpad" for testing EDO. +- (NSString*)testEDO; + +// Triggers an EXC_BAD_ACCESS exception and crash. +- (void)crashBadAccess; + +// Triggers a crash with a call to kill(SIGABRT). +- (void)crashKillAbort; + +// Triggers a segfault crash. +- (void)crashSegv; + +// Trigger a crash with a __builtin_trap. +- (void)crashTrap; + +// Trigger a crash with an abort(). +- (void)crashAbort; + +// Trigger a crash with an uncaught exception. +- (void)crashException; + +// Trigger a crash with an uncaught NSException. +- (void)crashNSException; + +// Trigger an unrecognized selector after delay. +- (void)crashUnreocgnizedSelectorAfterDelay; + +// Trigger a caught NSxception. +- (void)catchNSException; + +// Trigger a crash with an infinite recursion. +- (void)crashRecursion; +@end + +#endif // CRASHPAD_TEST_IOS_HOST_SHARED_OBJECT_H_ diff --git a/test/ios/host/main.mm b/test/ios/host/main.mm new file mode 100644 index 00000000..a0ae5c46 --- /dev/null +++ b/test/ios/host/main.mm @@ -0,0 +1,30 @@ +// Copyright 2020 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +#import "test/ios/host/cptest_application_delegate.h" + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +int main(int argc, char* argv[]) { + NSString* appDelegateClassName; + @autoreleasepool { + // Setup code that might create autoreleased objects goes here. + appDelegateClassName = NSStringFromClass([CPTestApplicationDelegate class]); + } + return UIApplicationMain(argc, argv, nil, appDelegateClassName); +} diff --git a/test/linux/fake_ptrace_connection.cc b/test/linux/fake_ptrace_connection.cc index 022b1327..b03228a2 100644 --- a/test/linux/fake_ptrace_connection.cc +++ b/test/linux/fake_ptrace_connection.cc @@ -92,5 +92,11 @@ ProcessMemory* FakePtraceConnection::Memory() { return memory_.get(); } +bool FakePtraceConnection::Threads(std::vector* threads) { + // TODO(jperaza): Implement this if/when it's needed. + NOTREACHED(); + return false; +} + } // namespace test } // namespace crashpad diff --git a/test/linux/fake_ptrace_connection.h b/test/linux/fake_ptrace_connection.h index 6597c473..01a6f925 100644 --- a/test/linux/fake_ptrace_connection.h +++ b/test/linux/fake_ptrace_connection.h @@ -62,6 +62,9 @@ class FakePtraceConnection : public PtraceConnection { //! ADD_FAILURE() and returning `nullptr` on failure. ProcessMemory* Memory() override; + //! \todo Not yet implemented. + bool Threads(std::vector* threads) override; + private: std::set attachments_; std::unique_ptr memory_; diff --git a/test/mac/exception_swallower.cc b/test/mac/exception_swallower.cc index 18c5cf71..946938fb 100644 --- a/test/mac/exception_swallower.cc +++ b/test/mac/exception_swallower.cc @@ -24,6 +24,7 @@ #include "base/mac/scoped_mach_port.h" #include "base/strings/stringprintf.h" #include "handler/mac/exception_handler_server.h" +#include "util/mach/bootstrap.h" #include "util/mach/exc_server_variants.h" #include "util/mach/exception_ports.h" #include "util/mach/mach_extensions.h" diff --git a/test/mac/mach_errors.cc b/test/mac/mach_errors.cc index b68448f1..db8ed666 100644 --- a/test/mac/mach_errors.cc +++ b/test/mac/mach_errors.cc @@ -14,8 +14,6 @@ #include "test/mac/mach_errors.h" -#include - #include "base/strings/stringprintf.h" namespace { @@ -50,28 +48,5 @@ std::string MachErrorMessage(mach_error_t mach_err, const std::string& base) { FormatMachErrorNumber(mach_err).c_str()); } -std::string BootstrapErrorMessage(kern_return_t bootstrap_err, - const std::string& base) { - switch (bootstrap_err) { - case BOOTSTRAP_SUCCESS: - case BOOTSTRAP_NOT_PRIVILEGED: - case BOOTSTRAP_NAME_IN_USE: - case BOOTSTRAP_UNKNOWN_SERVICE: - case BOOTSTRAP_SERVICE_ACTIVE: - case BOOTSTRAP_BAD_COUNT: - case BOOTSTRAP_NO_MEMORY: - case BOOTSTRAP_NO_CHILDREN: - // Show known bootstrap errors in decimal because that's how they're - // defined in . - return base::StringPrintf("%s%s (%d)", - FormatBase(base).c_str(), - bootstrap_strerror(bootstrap_err), - bootstrap_err); - - default: - return MachErrorMessage(bootstrap_err, base); - } -} - } // namespace test } // namespace crashpad diff --git a/test/mac/mach_errors.h b/test/mac/mach_errors.h index 9e43ef22..0afcdadb 100644 --- a/test/mac/mach_errors.h +++ b/test/mac/mach_errors.h @@ -22,9 +22,9 @@ namespace crashpad { namespace test { -// These functions format messages in a similar way to the logging macros in -// base/mac/mach_logging.h. They exist to interoperate with gtest assertions, -// which don’t interoperate with logging but can be streamed to. +// This function formats messages in a similar way to the Mach error logging +// macros in base/mac/mach_logging.h. It exists to interoperate with gtest +// assertions, which don’t interoperate with logging but can be streamed to. // // Where non-test code could do: // MACH_CHECK(kr == KERN_SUCCESS, kr) << "vm_deallocate"; @@ -47,23 +47,6 @@ namespace test { std::string MachErrorMessage(mach_error_t mach_err, const std::string& base = std::string()); -//! \brief Formats a bootstrap error message. -//! -//! The returned string will combine the \a base string, if supplied, with a -//! textual and numeric description of the error. -//! -//! \param[in] bootstrap_err The bootstrap error code. -//! \param[in] base A string to prepend to the error description. -//! -//! \return A string of the format `"Permission denied (1100)"` if \a -//! bootstrap_err has the value `BOOTSTRAP_NOT_PRIVILEGED` on a system where -//! this is defined to be 1100. If \a base is not empty, it will be -//! prepended to this string, separated by a colon. If \a bootstrap_err is -//! not a valid bootstrap error code, it will be interpreted as a Mach error -//! code in the manner of MachErrorMessage(). -std::string BootstrapErrorMessage(kern_return_t bootstrap_err, - const std::string& base = std::string()); - } // namespace test } // namespace crashpad diff --git a/test/mac/mach_multiprocess.cc b/test/mac/mach_multiprocess.cc index f29a8b0c..59aa653e 100644 --- a/test/mac/mach_multiprocess.cc +++ b/test/mac/mach_multiprocess.cc @@ -27,6 +27,7 @@ #include "test/errors.h" #include "test/mac/mach_errors.h" #include "util/file/file_io.h" +#include "util/mach/bootstrap.h" #include "util/mach/mach_extensions.h" #include "util/mach/mach_message.h" #include "util/misc/implicit_cast.h" diff --git a/test/multiprocess.h b/test/multiprocess.h index d0275020..61df98b5 100644 --- a/test/multiprocess.h +++ b/test/multiprocess.h @@ -15,6 +15,7 @@ #ifndef CRASHPAD_TEST_MULTIPROCESS_H_ #define CRASHPAD_TEST_MULTIPROCESS_H_ +#include #include #include "base/macros.h" @@ -26,7 +27,13 @@ namespace test { namespace internal { struct MultiprocessInfo; -}; +} // namespace internal + +#if defined(OS_FUCHSIA) +using ReturnCodeType = int64_t; +#else +using ReturnCodeType = int; +#endif //! \brief Manages a multiprocess test. //! @@ -76,7 +83,7 @@ class Multiprocess { //! \brief Sets the expected termination reason and code. //! - //! The default expected termination reasaon is + //! The default expected termination reason is //! TerminationReason::kTerminationNormal, and the default expected //! termination code is `EXIT_SUCCESS` (`0`). //! @@ -91,7 +98,8 @@ class Multiprocess { //! TerminationReason::kTerminationSignal, this is the signal that is //! expected to kill the child. On Linux platforms, SIG_DFL will be //! installed for \a code in the child process. - void SetExpectedChildTermination(TerminationReason reason, int code); + void SetExpectedChildTermination(TerminationReason reason, + ReturnCodeType code); #if !defined(OS_WIN) //! \brief Sets termination reason and code appropriately for a child that @@ -211,7 +219,7 @@ class Multiprocess { virtual void MultiprocessChild() = 0; internal::MultiprocessInfo* info_; - int code_; + ReturnCodeType code_; TerminationReason reason_; DISALLOW_COPY_AND_ASSIGN(Multiprocess); diff --git a/test/multiprocess_exec.h b/test/multiprocess_exec.h index 84335b28..1168f800 100644 --- a/test/multiprocess_exec.h +++ b/test/multiprocess_exec.h @@ -122,7 +122,9 @@ class MultiprocessExec : public Multiprocess { //! //! This method is only valid during the body of MultiprocessParent(). //! - //! \return A platform-specific type representing the child process. + //! \return A platform-specific type representing the child process. This + //! method can fail on macOS because access to a child's task port + //! requires the task_for_pid entitlement. ProcessType ChildProcess(); protected: diff --git a/test/multiprocess_exec_fuchsia.cc b/test/multiprocess_exec_fuchsia.cc index 95436b47..a1e2acf5 100644 --- a/test/multiprocess_exec_fuchsia.cc +++ b/test/multiprocess_exec_fuchsia.cc @@ -16,13 +16,12 @@ #include #include -#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 { @@ -31,14 +30,12 @@ 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"; + zx_handle_t handle = ZX_HANDLE_INVALID; + zx_status_t status = fdio_pipe_half(fd_out, &handle); + ZX_CHECK(status == ZX_OK, status) << "fdio_pipe_half"; action->action = FDIO_SPAWN_ACTION_ADD_HANDLE; - action->h.id = PA_HND(PA_HND_TYPE(id), target_fd); + action->h.id = PA_HND(PA_FD, target_fd); action->h.handle = handle; - *fd_out = status; } } // namespace @@ -49,7 +46,7 @@ struct MultiprocessInfo { MultiprocessInfo() {} base::ScopedFD stdin_write; base::ScopedFD stdout_read; - base::ScopedZxHandle child; + zx::process child; }; } // namespace internal @@ -68,19 +65,14 @@ void Multiprocess::Run() { // Wait until the child exits. zx_signals_t signals; ASSERT_EQ( - zx_object_wait_one( - info_->child.get(), ZX_TASK_TERMINATED, ZX_TIME_INFINITE, &signals), + info_->child.wait_one(ZX_TASK_TERMINATED, zx::time::infinite(), &signals), ZX_OK); ASSERT_EQ(signals, ZX_TASK_TERMINATED); // Get the child's exit code. zx_info_process_t proc_info; - zx_status_t status = zx_object_get_info(info_->child.get(), - ZX_INFO_PROCESS, - &proc_info, - sizeof(proc_info), - nullptr, - nullptr); + zx_status_t status = info_->child.get_info( + ZX_INFO_PROCESS, &proc_info, sizeof(proc_info), nullptr, nullptr); if (status != ZX_OK) { ZX_LOG(ERROR, status) << "zx_object_get_info"; ADD_FAILURE() << "Unable to get exit code of child"; @@ -93,7 +85,7 @@ void Multiprocess::Run() { } void Multiprocess::SetExpectedChildTermination(TerminationReason reason, - int code) { + ReturnCodeType code) { EXPECT_EQ(info_, nullptr) << "SetExpectedChildTermination() must be called before Run()"; reason_ = reason; @@ -101,7 +93,8 @@ void Multiprocess::SetExpectedChildTermination(TerminationReason reason, } void Multiprocess::SetExpectedChildTerminationBuiltinTrap() { - SetExpectedChildTermination(kTerminationNormal, -1); + constexpr ReturnCodeType kExpectedReturnCode = ZX_TASK_RETCODE_EXCEPTION_KILL; + SetExpectedChildTermination(kTerminationNormal, kExpectedReturnCode); } Multiprocess::~Multiprocess() { @@ -188,7 +181,7 @@ void MultiprocessExec::MultiprocessChild() { uint32_t flags = FDIO_SPAWN_CLONE_ALL & ~FDIO_SPAWN_CLONE_STDIO; char error_message[FDIO_SPAWN_ERR_MSG_MAX_LENGTH]; - zx_handle_t child; + zx::process child; zx_status_t status = fdio_spawn_etc(ZX_HANDLE_INVALID, flags, command_.value().c_str(), @@ -196,14 +189,14 @@ void MultiprocessExec::MultiprocessChild() { nullptr, kActionCount, actions, - &child, + child.reset_and_get_address(), error_message); ZX_CHECK(status == ZX_OK, status) << "fdio_spawn_etc: " << error_message; - info()->child.reset(child); + info()->child = std::move(child); } ProcessType MultiprocessExec::ChildProcess() { - return info()->child.get(); + return zx::unowned_process(info()->child); } } // namespace test diff --git a/test/multiprocess_exec_posix.cc b/test/multiprocess_exec_posix.cc index c91b7f73..534013ca 100644 --- a/test/multiprocess_exec_posix.cc +++ b/test/multiprocess_exec_posix.cc @@ -20,6 +20,7 @@ #include #include "base/posix/eintr_wrapper.h" +#include "build/build_config.h" #include "gtest/gtest.h" #include "test/errors.h" #include "util/misc/scoped_forbid_return.h" @@ -29,6 +30,10 @@ #include #endif +#if defined(OS_MACOSX) +#include "util/mach/task_for_pid.h" +#endif + namespace crashpad { namespace test { @@ -149,7 +154,11 @@ void MultiprocessExec::MultiprocessChild() { } ProcessType MultiprocessExec::ChildProcess() { +#if defined(OS_MACOSX) + return TaskForPID(ChildPID()); +#else return ChildPID(); +#endif } } // namespace test diff --git a/test/multiprocess_exec_test.cc b/test/multiprocess_exec_test.cc index 99a1b01f..d290efa8 100644 --- a/test/multiprocess_exec_test.cc +++ b/test/multiprocess_exec_test.cc @@ -72,8 +72,7 @@ TEST(MultiprocessExec, MultiprocessExecSimpleChild) { TestMultiprocessExec exec; exec.SetChildTestMainFunction("SimpleMultiprocess"); exec.Run(); -}; - +} CRASHPAD_CHILD_TEST_MAIN(SimpleMultiprocessReturnsNonZero) { return 123; @@ -96,7 +95,7 @@ TEST(MultiprocessExec, MultiprocessExecSimpleChildReturnsNonZero) { exec.SetExpectedChildTermination( Multiprocess::TerminationReason::kTerminationNormal, 123); exec.Run(); -}; +} #if !defined(OS_WIN) diff --git a/test/multiprocess_exec_win.cc b/test/multiprocess_exec_win.cc index 38978186..b75f8f57 100644 --- a/test/multiprocess_exec_win.cc +++ b/test/multiprocess_exec_win.cc @@ -58,7 +58,7 @@ void Multiprocess::Run() { } void Multiprocess::SetExpectedChildTermination(TerminationReason reason, - int code) { + ReturnCodeType code) { EXPECT_EQ(info_, nullptr) << "SetExpectedChildTermination() must be called before Run()"; reason_ = reason; diff --git a/test/multiprocess_posix.cc b/test/multiprocess_posix.cc index 2e0c3856..d2de3184 100644 --- a/test/multiprocess_posix.cc +++ b/test/multiprocess_posix.cc @@ -154,7 +154,7 @@ void Multiprocess::Run() { } void Multiprocess::SetExpectedChildTermination(TerminationReason reason, - int code) { + ReturnCodeType code) { EXPECT_EQ(info_, nullptr) << "SetExpectedChildTermination() must be called before Run()"; reason_ = reason; diff --git a/test/process_type.cc b/test/process_type.cc index 0e3c9e6d..94fd912d 100644 --- a/test/process_type.cc +++ b/test/process_type.cc @@ -15,8 +15,8 @@ #include "test/process_type.h" #if defined(OS_FUCHSIA) -#include -#elif defined(OS_POSIX) +#include +#elif defined(OS_LINUX) || defined(OS_ANDROID) #include #endif @@ -25,11 +25,13 @@ namespace test { ProcessType GetSelfProcess() { #if defined(OS_FUCHSIA) - return zx_process_self(); -#elif defined(OS_POSIX) + return zx::process::self(); +#elif defined(OS_LINUX) || defined(OS_ANDROID) return getpid(); #elif defined(OS_WIN) return GetCurrentProcess(); +#elif defined(OS_MACOSX) + return mach_task_self(); #endif } diff --git a/test/process_type.h b/test/process_type.h index a25b1222..dc99687a 100644 --- a/test/process_type.h +++ b/test/process_type.h @@ -18,23 +18,27 @@ #include "build/build_config.h" #if defined(OS_FUCHSIA) -#include -#elif defined(OS_POSIX) +#include +#elif defined(OS_LINUX) || defined(OS_ANDROID) #include #elif defined(OS_WIN) #include +#elif defined(OS_MACOSX) +#include #endif namespace crashpad { namespace test { #if defined(OS_FUCHSIA) -using ProcessType = zx_handle_t; -#elif defined(OS_POSIX) || DOXYGEN +using ProcessType = zx::unowned_process; +#elif defined(OS_LINUX) || defined(OS_ANDROID) || DOXYGEN //! \brief Alias for platform-specific type to represent a process. using ProcessType = pid_t; #elif defined(OS_WIN) using ProcessType = HANDLE; +#elif defined(OS_MACOSX) +using ProcessType = task_t; #else #error Port. #endif diff --git a/test/scoped_guarded_page.h b/test/scoped_guarded_page.h new file mode 100644 index 00000000..55ef2727 --- /dev/null +++ b/test/scoped_guarded_page.h @@ -0,0 +1,49 @@ +// Copyright 2018 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_TEST_SCOPED_GUARDED_PAGE_ +#define CRASHPAD_TEST_SCOPED_GUARDED_PAGE_ + +#include "base/macros.h" + +namespace crashpad { +namespace test { + +//! \brief A RAII object that allocates a read-write page with an inacessible +//! page following it. +//! +//! Upon construction, a mapping will be created. Failure to create the mapping +//! is fatal. On destruction, the mapping is freed. +//! +//! This object should not be used in multi-threded contexts, the POSIX +//! implementation can not be made thread-safe. +class ScopedGuardedPage { + public: + ScopedGuardedPage(); + ~ScopedGuardedPage(); + + //! \brief Returns the address of the read-write page. + //! + //! \return The address of the read-write page. + void* Pointer() const { return ptr_; } + + private: + void* ptr_; + DISALLOW_COPY_AND_ASSIGN(ScopedGuardedPage); +}; + +} // namespace test +} // namespace crashpad + +#endif // CRASHPAD_TEST_SCOPED_GUARDED_PAGE_ diff --git a/test/scoped_guarded_page_posix.cc b/test/scoped_guarded_page_posix.cc new file mode 100644 index 00000000..750fe181 --- /dev/null +++ b/test/scoped_guarded_page_posix.cc @@ -0,0 +1,47 @@ +// Copyright 2018 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "test/scoped_guarded_page.h" + +#include + +#include "base/logging.h" +#include "base/process/process_metrics.h" + +namespace crashpad { +namespace test { + +ScopedGuardedPage::ScopedGuardedPage() { + ptr_ = mmap(nullptr, + base::GetPageSize() * 2, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, + -1, + 0); + PCHECK(ptr_ != MAP_FAILED) << "mmap"; + + // Simply mprotect()ing the guard page PROT_NONE does not make it inaccessible + // using ptrace() or /proc/$pid/mem so we munmap() the following page instead. + // Unfortunately, this means that the guarded page is not thread safe from + // other threads mapping a single page into the empty region. + char* second_page = static_cast(ptr_) + base::GetPageSize(); + PCHECK(munmap(second_page, base::GetPageSize()) >= 0) << "munmap"; +} + +ScopedGuardedPage::~ScopedGuardedPage() { + PCHECK(munmap(ptr_, base::GetPageSize()) >= 0) << "munmap"; +} + +} // namespace test +} // namespace crashpad diff --git a/test/scoped_guarded_page_test.cc b/test/scoped_guarded_page_test.cc new file mode 100644 index 00000000..023d1ed1 --- /dev/null +++ b/test/scoped_guarded_page_test.cc @@ -0,0 +1,38 @@ +// Copyright 2018 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "test/scoped_guarded_page.h" + +#include "base/process/process_metrics.h" +#include "gtest/gtest.h" +#include "test/gtest_death.h" + +namespace crashpad { +namespace test { +namespace { + +TEST(ScopedGuardedPage, BasicFunctionality) { + ::testing::FLAGS_gtest_death_test_style = "threadsafe"; + + ScopedGuardedPage page; + char* address = (char*)page.Pointer(); + EXPECT_NE(address, nullptr); + address[0] = 0; + address[base::GetPageSize() - 1] = 0; + EXPECT_DEATH_CRASH({ address[base::GetPageSize()] = 0; }, ""); +} + +} // namespace +} // namespace test +} // namespace crashpad diff --git a/test/scoped_guarded_page_win.cc b/test/scoped_guarded_page_win.cc new file mode 100644 index 00000000..23a68977 --- /dev/null +++ b/test/scoped_guarded_page_win.cc @@ -0,0 +1,39 @@ +// Copyright 2018 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "test/scoped_guarded_page.h" + +#include + +#include "base/logging.h" +#include "base/process/process_metrics.h" + +namespace crashpad { +namespace test { + +ScopedGuardedPage::ScopedGuardedPage() { + const size_t page_size = base::GetPageSize(); + ptr_ = VirtualAlloc(nullptr, page_size * 2, MEM_RESERVE, PAGE_NOACCESS); + PCHECK(ptr_ != nullptr) << "VirtualAlloc"; + + PCHECK(VirtualAlloc(ptr_, page_size, MEM_COMMIT, PAGE_READWRITE) != nullptr) + << "VirtualAlloc"; +} + +ScopedGuardedPage::~ScopedGuardedPage() { + PCHECK(VirtualFree(ptr_, 0, MEM_RELEASE)) << "VirtualFree"; +} + +} // namespace test +} // namespace crashpad diff --git a/test/scoped_temp_dir_win.cc b/test/scoped_temp_dir_win.cc index ab4ff957..8031921c 100644 --- a/test/scoped_temp_dir_win.cc +++ b/test/scoped_temp_dir_win.cc @@ -64,7 +64,7 @@ base::FilePath ScopedTempDir::CreateTemporaryDirectory() { // the one we generate exists, keep trying another path name until we reach // some limit. base::FilePath path_to_create = GenerateCandidateName(); - if (CreateDirectory(path_to_create.value().c_str(), NULL)) + if (CreateDirectory(path_to_create.value().c_str(), nullptr)) return path_to_create; } diff --git a/test/test.gyp b/test/test.gyp index 29737088..d00256a7 100644 --- a/test/test.gyp +++ b/test/test.gyp @@ -37,8 +37,6 @@ 'filesystem.cc', 'filesystem.h', 'gtest_death.h', - 'gtest_disabled.cc', - 'gtest_disabled.h', 'hex_string.cc', 'hex_string.h', 'linux/fake_ptrace_connection.cc', @@ -63,6 +61,8 @@ 'multiprocess_posix.cc', 'process_type.cc', 'process_type.h', + 'scoped_guarded_page.h', + 'scoped_guarded_page_posix.cc', 'scoped_module_handle.cc', 'scoped_module_handle.h', 'scoped_temp_dir.cc', diff --git a/test/test_paths.cc b/test/test_paths.cc index 50165e43..475e2b01 100644 --- a/test/test_paths.cc +++ b/test/test_paths.cc @@ -44,16 +44,9 @@ 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 + base::FilePath asset_path("/pkg/data"); if (!IsTestDataRoot(asset_path)) { - LOG(WARNING) << "Test data root seems invalid, continuing anyway"; + LOG(WARNING) << "test data root seems invalid, continuing anyway"; } return asset_path; #else // defined(OS_FUCHSIA) @@ -72,14 +65,23 @@ base::FilePath TestDataRootInternal() { return base::FilePath(environment_value); } - // In a standalone build, the test executable is usually at - // out/{Debug,Release} relative to the Crashpad root. base::FilePath executable_path; if (Paths::Executable(&executable_path)) { +#if defined(OS_IOS) || defined(OS_ANDROID) + // On Android and iOS, test data is in a crashpad_test_data directory + // adjacent to the main executable. On iOS, this refers to the main + // executable file inside the .app bundle, so crashpad_test_data is also + // inside the bundle. + base::FilePath candidate = executable_path.DirName() + .Append("crashpad_test_data"); +#else // OS_IOS || OS_ANDRID + // In a standalone build, the test executable is usually at + // out/{Debug,Release} relative to the Crashpad root. base::FilePath candidate = base::FilePath(executable_path.DirName() .Append(base::FilePath::kParentDirectory) .Append(base::FilePath::kParentDirectory)); +#endif // OS_IOS || OS_ANDROID if (IsTestDataRoot(candidate)) { return candidate; } @@ -129,11 +131,7 @@ base::FilePath 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"); + executable_path = base::FilePath("/pkg/bin/app"); #endif return executable_path; } @@ -202,16 +200,7 @@ base::FilePath TestPaths::BuildArtifact( #if defined(OS_WIN) extension = FILE_PATH_LITERAL(".exe"); #elif defined(OS_FUCHSIA) -#if defined(CRASHPAD_IS_IN_FUCHSIA) - // Tests are not yet packaged when running in the Fuchsia tree, so - // binaries do not appear as expected at /pkg/bin. Override the default of - // /pkg/bin/app so that tests can find the correct location for now. - // https://crashpad.chromium.org/bug/196. - directory = - base::FilePath(FILE_PATH_LITERAL("/system/test/crashpad_test_data")); -#else directory = base::FilePath(FILE_PATH_LITERAL("/pkg/bin")); -#endif #endif // OS_WIN break; @@ -233,12 +222,7 @@ base::FilePath TestPaths::BuildArtifact( case FileType::kCertificate: #if defined(CRASHPAD_IS_IN_FUCHSIA) - // When running in the Fuchsia tree, the .pem files are packaged as assets - // into the test data folder. This will need to be rationalized when - // things are actually run from a package. - // https://crashpad.chromium.org/bug/196. - directory = - base::FilePath(FILE_PATH_LITERAL("/system/test/crashpad_test_data")); + directory = base::FilePath(FILE_PATH_LITERAL("/pkg/data")); #endif extension = FILE_PATH_LITERAL(".pem"); break; diff --git a/test/test_test.gyp b/test/test_test.gyp index 3c6d7066..ddd920e9 100644 --- a/test/test_test.gyp +++ b/test/test_test.gyp @@ -39,6 +39,7 @@ 'main_arguments_test.cc', 'multiprocess_exec_test.cc', 'multiprocess_posix_test.cc', + 'scoped_guarded_page_test.cc', 'scoped_temp_dir_test.cc', 'test_paths_test.cc', 'win/win_child_process_test.cc', diff --git a/test/win/win_multiprocess_with_temp_dir.cc b/test/win/win_multiprocess_with_temp_dir.cc index b61ebf62..ca1e01a5 100644 --- a/test/win/win_multiprocess_with_temp_dir.cc +++ b/test/win/win_multiprocess_with_temp_dir.cc @@ -17,6 +17,7 @@ #include #include "test/errors.h" +#include "util/process/process_id.h" #include "util/win/process_info.h" namespace crashpad { @@ -28,20 +29,20 @@ constexpr wchar_t kTempDirEnvName[] = L"CRASHPAD_TEST_TEMP_DIR"; // Returns the process IDs of all processes that have |parent_pid| as // parent process ID. -std::vector GetPotentialChildProcessesOf(pid_t parent_pid) { +std::vector GetPotentialChildProcessesOf(ProcessID parent_pid) { ScopedFileHANDLE snapshot(CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)); if (!snapshot.is_valid()) { ADD_FAILURE() << ErrorMessage("CreateToolhelp32Snapshot"); - return std::vector(); + return std::vector(); } PROCESSENTRY32 entry = {sizeof(entry)}; if (!Process32First(snapshot.get(), &entry)) { ADD_FAILURE() << ErrorMessage("Process32First"); - return std::vector(); + return std::vector(); } - std::vector child_pids; + std::vector child_pids; do { if (entry.th32ParentProcessID == parent_pid) child_pids.push_back(entry.th32ProcessID); @@ -68,11 +69,11 @@ ULARGE_INTEGER GetProcessCreationTime(HANDLE process) { // not their offspring. For this to work without race, |parent| has to be // suspended or have exited. void WaitForAllChildProcessesOf(HANDLE parent) { - pid_t parent_pid = GetProcessId(parent); - std::vector child_pids = GetPotentialChildProcessesOf(parent_pid); + ProcessID parent_pid = GetProcessId(parent); + std::vector child_pids = GetPotentialChildProcessesOf(parent_pid); ULARGE_INTEGER parent_creationtime = GetProcessCreationTime(parent); - for (pid_t child_pid : child_pids) { + for (ProcessID child_pid : child_pids) { // Try and open the process. This may fail for reasons such as: // 1. The process isn't |parent|'s child process, but rather a // higher-privilege sub-process of an earlier process that had diff --git a/third_party/apple_cf/BUILD.gn b/third_party/apple_cf/BUILD.gn new file mode 100644 index 00000000..9e37354d --- /dev/null +++ b/third_party/apple_cf/BUILD.gn @@ -0,0 +1,17 @@ +# Copyright 2019 The Crashpad Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +source_set("apple_cf") { + sources = [ "CFStreamAbstract.h" ] +} diff --git a/third_party/apple_cf/CFStreamAbstract.h b/third_party/apple_cf/CFStreamAbstract.h index bf12c928..ac26664f 100644 --- a/third_party/apple_cf/CFStreamAbstract.h +++ b/third_party/apple_cf/CFStreamAbstract.h @@ -1,15 +1,15 @@ /* - * Copyright (c) 2010 Apple Inc. All rights reserved. + * Copyright (c) 2015 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ - * + * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. - * + * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, @@ -17,12 +17,12 @@ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. - * + * * @APPLE_LICENSE_HEADER_END@ */ /* CFStreamAbstract.h - Copyright (c) 2000-2009, Apple Inc. All rights reserved. + Copyright (c) 2000-2014, Apple Inc. All rights reserved. */ #if !defined(__COREFOUNDATION_CFSTREAMABSTRACT__) @@ -134,11 +134,18 @@ CF_EXPORT void *_CFWriteStreamGetClient(CFWriteStreamRef writeStream); // Returns an array of the runloops and modes on which the stream is currently scheduled +// Note that these are unretained mutable arrays - use the copy variant instead. CF_EXPORT CFArrayRef _CFReadStreamGetRunLoopsAndModes(CFReadStreamRef readStream); CF_EXPORT CFArrayRef _CFWriteStreamGetRunLoopsAndModes(CFWriteStreamRef writeStream); +// Returns an array of the runloops and modes on which the stream is currently scheduled +CF_EXPORT +CFArrayRef _CFReadStreamCopyRunLoopsAndModes(CFReadStreamRef readStream); +CF_EXPORT +CFArrayRef _CFWriteStreamCopyRunLoopsAndModes(CFWriteStreamRef writeStream); + /* Deprecated versions; here for backwards compatibility. */ typedef struct { CFIndex version; /* == 1 */ diff --git a/third_party/apple_cf/README.crashpad b/third_party/apple_cf/README.crashpad index b6c6cfad..20066484 100644 --- a/third_party/apple_cf/README.crashpad +++ b/third_party/apple_cf/README.crashpad @@ -2,7 +2,7 @@ Name: Apple CF-Lite Short Name: CF URL: https://opensource.apple.com/source/CF/ URL: https://opensource.apple.com/tarballs/CF/ -Version: 550.43 (from Mac OS X 10.6.8) +Version: 1153.18 (from OS X 10.10.3) License: APSL 2.0 License File: APPLE_LICENSE Security Critical: no diff --git a/third_party/cpp-httplib/BUILD.gn b/third_party/cpp-httplib/BUILD.gn index be2a6d74..2cbb72f5 100644 --- a/third_party/cpp-httplib/BUILD.gn +++ b/third_party/cpp-httplib/BUILD.gn @@ -15,7 +15,5 @@ source_set("cpp-httplib") { testonly = true include_dirs = [ "cpp-httplib" ] - sources = [ - "cpp-httplib/httplib.h", - ] + sources = [ "cpp-httplib/httplib.h" ] } diff --git a/third_party/edo/BUILD.gn b/third_party/edo/BUILD.gn new file mode 100644 index 00000000..0205489f --- /dev/null +++ b/third_party/edo/BUILD.gn @@ -0,0 +1,142 @@ +# Copyright 2018 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("../../build/crashpad_buildconfig.gni") + +if (crashpad_is_in_chromium) { + group("edo") { + testonly = true + public_deps = [ + "//ios/third_party/edo", + ] + } +} else { + config("config") { + include_dirs = [ "../../third_party/edo/edo" ] + } + + source_set("edo") { + testonly = true + + sources = [ + "edo/Channel/Sources/EDOBlockingQueue.h", + "edo/Channel/Sources/EDOBlockingQueue.m", + "edo/Channel/Sources/EDOChannel.h", + "edo/Channel/Sources/EDOChannelErrors.h", + "edo/Channel/Sources/EDOChannelErrors.m", + "edo/Channel/Sources/EDOChannelForwarder.h", + "edo/Channel/Sources/EDOChannelForwarder.m", + "edo/Channel/Sources/EDOChannelMultiplexer.h", + "edo/Channel/Sources/EDOChannelMultiplexer.m", + "edo/Channel/Sources/EDOChannelPool.h", + "edo/Channel/Sources/EDOChannelPool.m", + "edo/Channel/Sources/EDOChannelUtil.h", + "edo/Channel/Sources/EDOChannelUtil.m", + "edo/Channel/Sources/EDOHostPort.h", + "edo/Channel/Sources/EDOHostPort.m", + "edo/Channel/Sources/EDOListenSocket.h", + "edo/Channel/Sources/EDOListenSocket.m", + "edo/Channel/Sources/EDOSocket.h", + "edo/Channel/Sources/EDOSocket.m", + "edo/Channel/Sources/EDOSocketChannel.h", + "edo/Channel/Sources/EDOSocketChannel.m", + "edo/Channel/Sources/EDOSocketPort.h", + "edo/Channel/Sources/EDOSocketPort.m", + "edo/Device/Sources/EDODeviceChannel.h", + "edo/Device/Sources/EDODeviceChannel.m", + "edo/Device/Sources/EDODeviceConnector.h", + "edo/Device/Sources/EDODeviceConnector.m", + "edo/Device/Sources/EDODeviceDetector.h", + "edo/Device/Sources/EDODeviceDetector.m", + "edo/Device/Sources/EDOUSBMuxUtil.h", + "edo/Device/Sources/EDOUSBMuxUtil.m", + "edo/DeviceForwarder/Sources/EDODeviceForwardersManager.h", + "edo/DeviceForwarder/Sources/EDODeviceForwardersManager.m", + "edo/Measure/Sources/EDONumericMeasure.h", + "edo/Measure/Sources/EDONumericMeasure.m", + "edo/Service/Sources/EDOBlockObject.h", + "edo/Service/Sources/EDOBlockObject.m", + "edo/Service/Sources/EDOClassMessage.h", + "edo/Service/Sources/EDOClassMessage.m", + "edo/Service/Sources/EDOClientService+Private.h", + "edo/Service/Sources/EDOClientService.h", + "edo/Service/Sources/EDOClientService.m", + "edo/Service/Sources/EDOClientServiceStatsCollector.h", + "edo/Service/Sources/EDOClientServiceStatsCollector.m", + "edo/Service/Sources/EDODeallocationTracker.h", + "edo/Service/Sources/EDODeallocationTracker.m", + "edo/Service/Sources/EDOExecutor.h", + "edo/Service/Sources/EDOExecutor.m", + "edo/Service/Sources/EDOExecutorMessage.h", + "edo/Service/Sources/EDOExecutorMessage.m", + "edo/Service/Sources/EDOHostNamingService+Private.h", + "edo/Service/Sources/EDOHostNamingService.h", + "edo/Service/Sources/EDOHostNamingService.m", + "edo/Service/Sources/EDOHostService+Handlers.h", + "edo/Service/Sources/EDOHostService+Handlers.m", + "edo/Service/Sources/EDOHostService+Private.h", + "edo/Service/Sources/EDOHostService.h", + "edo/Service/Sources/EDOHostService.m", + "edo/Service/Sources/EDOInvocationMessage.h", + "edo/Service/Sources/EDOInvocationMessage.m", + "edo/Service/Sources/EDOMessage.h", + "edo/Service/Sources/EDOMessage.m", + "edo/Service/Sources/EDOMethodSignatureMessage.h", + "edo/Service/Sources/EDOMethodSignatureMessage.m", + "edo/Service/Sources/EDOObject+EDOParameter.m", + "edo/Service/Sources/EDOObject+Invocation.m", + "edo/Service/Sources/EDOObject+Private.h", + "edo/Service/Sources/EDOObject.h", + "edo/Service/Sources/EDOObject.m", + "edo/Service/Sources/EDOObjectAliveMessage.h", + "edo/Service/Sources/EDOObjectAliveMessage.m", + "edo/Service/Sources/EDOObjectMessage.h", + "edo/Service/Sources/EDOObjectMessage.m", + "edo/Service/Sources/EDOObjectReleaseMessage.h", + "edo/Service/Sources/EDOObjectReleaseMessage.m", + "edo/Service/Sources/EDOParameter.h", + "edo/Service/Sources/EDOParameter.m", + "edo/Service/Sources/EDOProtocolObject.h", + "edo/Service/Sources/EDOProtocolObject.m", + "edo/Service/Sources/EDORemoteVariable.h", + "edo/Service/Sources/EDORemoteVariable.m", + "edo/Service/Sources/EDOServiceError.h", + "edo/Service/Sources/EDOServiceError.m", + "edo/Service/Sources/EDOServiceException.h", + "edo/Service/Sources/EDOServiceException.m", + "edo/Service/Sources/EDOServicePort.h", + "edo/Service/Sources/EDOServicePort.m", + "edo/Service/Sources/EDOServiceRequest.h", + "edo/Service/Sources/EDOServiceRequest.m", + "edo/Service/Sources/EDOTimingFunctions.h", + "edo/Service/Sources/EDOTimingFunctions.m", + "edo/Service/Sources/EDOValueObject+EDOParameter.m", + "edo/Service/Sources/EDOValueObject.h", + "edo/Service/Sources/EDOValueObject.m", + "edo/Service/Sources/EDOValueType.m", + "edo/Service/Sources/EDOWeakObject.h", + "edo/Service/Sources/EDOWeakObject.m", + "edo/Service/Sources/NSBlock+EDOInvocation.m", + "edo/Service/Sources/NSKeyedArchiver+EDOAdditions.h", + "edo/Service/Sources/NSKeyedArchiver+EDOAdditions.m", + "edo/Service/Sources/NSKeyedUnarchiver+EDOAdditions.h", + "edo/Service/Sources/NSKeyedUnarchiver+EDOAdditions.m", + "edo/Service/Sources/NSObject+EDOParameter.h", + "edo/Service/Sources/NSObject+EDOParameter.m", + "edo/Service/Sources/NSObject+EDOValue.h", + "edo/Service/Sources/NSObject+EDOValue.m", + "edo/Service/Sources/NSObject+EDOValueObject.h", + "edo/Service/Sources/NSObject+EDOValueObject.m", + "edo/Service/Sources/NSObject+EDOWeakObject.h", + "edo/Service/Sources/NSObject+EDOWeakObject.m", + "edo/Service/Sources/NSProxy+EDOParameter.h", + "edo/Service/Sources/NSProxy+EDOParameter.m", + ] + + public_configs = [ ":config" ] + deps = [ + "../../build:ios_enable_arc", + ] + } +} diff --git a/third_party/edo/README.crashpad b/third_party/edo/README.crashpad new file mode 100644 index 00000000..fb831582 --- /dev/null +++ b/third_party/edo/README.crashpad @@ -0,0 +1,10 @@ +Name: eDistantObject +Short Name: EDO +URL: https://github.com/google/eDistantObject +Revision: See DEPS +License: Apache 2.0 +License File: edo/LICENSE +Security Critical: no + +Description: +iOS remote method invocations (distant object) over Inter-process communication layer. diff --git a/third_party/fuchsia/BUILD.gn b/third_party/fuchsia/BUILD.gn new file mode 100644 index 00000000..7e06289c --- /dev/null +++ b/third_party/fuchsia/BUILD.gn @@ -0,0 +1,38 @@ +# Copyright 2018 The Crashpad Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("../../build/crashpad_buildconfig.gni") + +if (crashpad_is_in_fuchsia) { + group("fuchsia") { + public_deps = [ + "//zircon/public/lib/fdio", + "//zircon/public/lib/zx", + ] + } +} else if (crashpad_is_in_chromium) { + group("fuchsia") { + public_deps = [ + "//third_party/fuchsia-sdk/sdk/pkg/fdio", + "//third_party/fuchsia-sdk/sdk/pkg/zx", + ] + } +} else { + group("fuchsia") { + public_deps = [ + "//third_party/fuchsia/sdk/$host_os-amd64/pkg/fdio", + "//third_party/fuchsia/sdk/$host_os-amd64/pkg/zx", + ] + } +} diff --git a/third_party/fuchsia/runner.py b/third_party/fuchsia/runner.py new file mode 100755 index 00000000..229d8a42 --- /dev/null +++ b/third_party/fuchsia/runner.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python + +# Copyright 2018 The Crashpad Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import sys + +os.execv(sys.argv[1], sys.argv[1:]) diff --git a/third_party/glibc/BUILD.gn b/third_party/glibc/BUILD.gn new file mode 100644 index 00000000..3f2b08bc --- /dev/null +++ b/third_party/glibc/BUILD.gn @@ -0,0 +1,17 @@ +# Copyright 2019 The Crashpad Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +source_set("glibc") { + sources = [ "elf/elf.h" ] +} diff --git a/third_party/glibc/COPYING.LIB b/third_party/glibc/COPYING.LIB new file mode 100644 index 00000000..4362b491 --- /dev/null +++ b/third_party/glibc/COPYING.LIB @@ -0,0 +1,502 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/third_party/glibc/README.crashpad b/third_party/glibc/README.crashpad new file mode 100644 index 00000000..b550ad98 --- /dev/null +++ b/third_party/glibc/README.crashpad @@ -0,0 +1,16 @@ +Name: GNU C Library +Short Name: glibc +URL: https://www.gnu.org/software/libc/ +URL: https://sourceware.org/git/?p=glibc.git +Version: 2.29 +License: GNU LGPL 2.1 +License File: COPYING.LIB +Security Critical: no + +Description: +glibc is the GNU Project’s implementation of the C standard library. + +Local Modifications: + - Only elf/elf.h is included. Its #include of has been removed, + and it uses of __BEGIN_DECLS and __END_DECLS have been replaced with inline + versions in the manner that misc/sys/cdefs.h defines those macros. diff --git a/third_party/glibc/elf/elf.h b/third_party/glibc/elf/elf.h new file mode 100644 index 00000000..331536b4 --- /dev/null +++ b/third_party/glibc/elf/elf.h @@ -0,0 +1,4003 @@ +/* This file defines standard ELF types, structures, and macros. + Copyright (C) 1995-2019 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#ifndef _ELF_H +#define _ELF_H 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/* Standard ELF types. */ + +#include + +/* Type for a 16-bit quantity. */ +typedef uint16_t Elf32_Half; +typedef uint16_t Elf64_Half; + +/* Types for signed and unsigned 32-bit quantities. */ +typedef uint32_t Elf32_Word; +typedef int32_t Elf32_Sword; +typedef uint32_t Elf64_Word; +typedef int32_t Elf64_Sword; + +/* Types for signed and unsigned 64-bit quantities. */ +typedef uint64_t Elf32_Xword; +typedef int64_t Elf32_Sxword; +typedef uint64_t Elf64_Xword; +typedef int64_t Elf64_Sxword; + +/* Type of addresses. */ +typedef uint32_t Elf32_Addr; +typedef uint64_t Elf64_Addr; + +/* Type of file offsets. */ +typedef uint32_t Elf32_Off; +typedef uint64_t Elf64_Off; + +/* Type for section indices, which are 16-bit quantities. */ +typedef uint16_t Elf32_Section; +typedef uint16_t Elf64_Section; + +/* Type for version symbol information. */ +typedef Elf32_Half Elf32_Versym; +typedef Elf64_Half Elf64_Versym; + + +/* The ELF file header. This appears at the start of every ELF file. */ + +#define EI_NIDENT (16) + +typedef struct +{ + unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */ + Elf32_Half e_type; /* Object file type */ + Elf32_Half e_machine; /* Architecture */ + Elf32_Word e_version; /* Object file version */ + Elf32_Addr e_entry; /* Entry point virtual address */ + Elf32_Off e_phoff; /* Program header table file offset */ + Elf32_Off e_shoff; /* Section header table file offset */ + Elf32_Word e_flags; /* Processor-specific flags */ + Elf32_Half e_ehsize; /* ELF header size in bytes */ + Elf32_Half e_phentsize; /* Program header table entry size */ + Elf32_Half e_phnum; /* Program header table entry count */ + Elf32_Half e_shentsize; /* Section header table entry size */ + Elf32_Half e_shnum; /* Section header table entry count */ + Elf32_Half e_shstrndx; /* Section header string table index */ +} Elf32_Ehdr; + +typedef struct +{ + unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */ + Elf64_Half e_type; /* Object file type */ + Elf64_Half e_machine; /* Architecture */ + Elf64_Word e_version; /* Object file version */ + Elf64_Addr e_entry; /* Entry point virtual address */ + Elf64_Off e_phoff; /* Program header table file offset */ + Elf64_Off e_shoff; /* Section header table file offset */ + Elf64_Word e_flags; /* Processor-specific flags */ + Elf64_Half e_ehsize; /* ELF header size in bytes */ + Elf64_Half e_phentsize; /* Program header table entry size */ + Elf64_Half e_phnum; /* Program header table entry count */ + Elf64_Half e_shentsize; /* Section header table entry size */ + Elf64_Half e_shnum; /* Section header table entry count */ + Elf64_Half e_shstrndx; /* Section header string table index */ +} Elf64_Ehdr; + +/* Fields in the e_ident array. The EI_* macros are indices into the + array. The macros under each EI_* macro are the values the byte + may have. */ + +#define EI_MAG0 0 /* File identification byte 0 index */ +#define ELFMAG0 0x7f /* Magic number byte 0 */ + +#define EI_MAG1 1 /* File identification byte 1 index */ +#define ELFMAG1 'E' /* Magic number byte 1 */ + +#define EI_MAG2 2 /* File identification byte 2 index */ +#define ELFMAG2 'L' /* Magic number byte 2 */ + +#define EI_MAG3 3 /* File identification byte 3 index */ +#define ELFMAG3 'F' /* Magic number byte 3 */ + +/* Conglomeration of the identification bytes, for easy testing as a word. */ +#define ELFMAG "\177ELF" +#define SELFMAG 4 + +#define EI_CLASS 4 /* File class byte index */ +#define ELFCLASSNONE 0 /* Invalid class */ +#define ELFCLASS32 1 /* 32-bit objects */ +#define ELFCLASS64 2 /* 64-bit objects */ +#define ELFCLASSNUM 3 + +#define EI_DATA 5 /* Data encoding byte index */ +#define ELFDATANONE 0 /* Invalid data encoding */ +#define ELFDATA2LSB 1 /* 2's complement, little endian */ +#define ELFDATA2MSB 2 /* 2's complement, big endian */ +#define ELFDATANUM 3 + +#define EI_VERSION 6 /* File version byte index */ + /* Value must be EV_CURRENT */ + +#define EI_OSABI 7 /* OS ABI identification */ +#define ELFOSABI_NONE 0 /* UNIX System V ABI */ +#define ELFOSABI_SYSV 0 /* Alias. */ +#define ELFOSABI_HPUX 1 /* HP-UX */ +#define ELFOSABI_NETBSD 2 /* NetBSD. */ +#define ELFOSABI_GNU 3 /* Object uses GNU ELF extensions. */ +#define ELFOSABI_LINUX ELFOSABI_GNU /* Compatibility alias. */ +#define ELFOSABI_SOLARIS 6 /* Sun Solaris. */ +#define ELFOSABI_AIX 7 /* IBM AIX. */ +#define ELFOSABI_IRIX 8 /* SGI Irix. */ +#define ELFOSABI_FREEBSD 9 /* FreeBSD. */ +#define ELFOSABI_TRU64 10 /* Compaq TRU64 UNIX. */ +#define ELFOSABI_MODESTO 11 /* Novell Modesto. */ +#define ELFOSABI_OPENBSD 12 /* OpenBSD. */ +#define ELFOSABI_ARM_AEABI 64 /* ARM EABI */ +#define ELFOSABI_ARM 97 /* ARM */ +#define ELFOSABI_STANDALONE 255 /* Standalone (embedded) application */ + +#define EI_ABIVERSION 8 /* ABI version */ + +#define EI_PAD 9 /* Byte index of padding bytes */ + +/* Legal values for e_type (object file type). */ + +#define ET_NONE 0 /* No file type */ +#define ET_REL 1 /* Relocatable file */ +#define ET_EXEC 2 /* Executable file */ +#define ET_DYN 3 /* Shared object file */ +#define ET_CORE 4 /* Core file */ +#define ET_NUM 5 /* Number of defined types */ +#define ET_LOOS 0xfe00 /* OS-specific range start */ +#define ET_HIOS 0xfeff /* OS-specific range end */ +#define ET_LOPROC 0xff00 /* Processor-specific range start */ +#define ET_HIPROC 0xffff /* Processor-specific range end */ + +/* Legal values for e_machine (architecture). */ + +#define EM_NONE 0 /* No machine */ +#define EM_M32 1 /* AT&T WE 32100 */ +#define EM_SPARC 2 /* SUN SPARC */ +#define EM_386 3 /* Intel 80386 */ +#define EM_68K 4 /* Motorola m68k family */ +#define EM_88K 5 /* Motorola m88k family */ +#define EM_IAMCU 6 /* Intel MCU */ +#define EM_860 7 /* Intel 80860 */ +#define EM_MIPS 8 /* MIPS R3000 big-endian */ +#define EM_S370 9 /* IBM System/370 */ +#define EM_MIPS_RS3_LE 10 /* MIPS R3000 little-endian */ + /* reserved 11-14 */ +#define EM_PARISC 15 /* HPPA */ + /* reserved 16 */ +#define EM_VPP500 17 /* Fujitsu VPP500 */ +#define EM_SPARC32PLUS 18 /* Sun's "v8plus" */ +#define EM_960 19 /* Intel 80960 */ +#define EM_PPC 20 /* PowerPC */ +#define EM_PPC64 21 /* PowerPC 64-bit */ +#define EM_S390 22 /* IBM S390 */ +#define EM_SPU 23 /* IBM SPU/SPC */ + /* reserved 24-35 */ +#define EM_V800 36 /* NEC V800 series */ +#define EM_FR20 37 /* Fujitsu FR20 */ +#define EM_RH32 38 /* TRW RH-32 */ +#define EM_RCE 39 /* Motorola RCE */ +#define EM_ARM 40 /* ARM */ +#define EM_FAKE_ALPHA 41 /* Digital Alpha */ +#define EM_SH 42 /* Hitachi SH */ +#define EM_SPARCV9 43 /* SPARC v9 64-bit */ +#define EM_TRICORE 44 /* Siemens Tricore */ +#define EM_ARC 45 /* Argonaut RISC Core */ +#define EM_H8_300 46 /* Hitachi H8/300 */ +#define EM_H8_300H 47 /* Hitachi H8/300H */ +#define EM_H8S 48 /* Hitachi H8S */ +#define EM_H8_500 49 /* Hitachi H8/500 */ +#define EM_IA_64 50 /* Intel Merced */ +#define EM_MIPS_X 51 /* Stanford MIPS-X */ +#define EM_COLDFIRE 52 /* Motorola Coldfire */ +#define EM_68HC12 53 /* Motorola M68HC12 */ +#define EM_MMA 54 /* Fujitsu MMA Multimedia Accelerator */ +#define EM_PCP 55 /* Siemens PCP */ +#define EM_NCPU 56 /* Sony nCPU embeeded RISC */ +#define EM_NDR1 57 /* Denso NDR1 microprocessor */ +#define EM_STARCORE 58 /* Motorola Start*Core processor */ +#define EM_ME16 59 /* Toyota ME16 processor */ +#define EM_ST100 60 /* STMicroelectronic ST100 processor */ +#define EM_TINYJ 61 /* Advanced Logic Corp. Tinyj emb.fam */ +#define EM_X86_64 62 /* AMD x86-64 architecture */ +#define EM_PDSP 63 /* Sony DSP Processor */ +#define EM_PDP10 64 /* Digital PDP-10 */ +#define EM_PDP11 65 /* Digital PDP-11 */ +#define EM_FX66 66 /* Siemens FX66 microcontroller */ +#define EM_ST9PLUS 67 /* STMicroelectronics ST9+ 8/16 mc */ +#define EM_ST7 68 /* STmicroelectronics ST7 8 bit mc */ +#define EM_68HC16 69 /* Motorola MC68HC16 microcontroller */ +#define EM_68HC11 70 /* Motorola MC68HC11 microcontroller */ +#define EM_68HC08 71 /* Motorola MC68HC08 microcontroller */ +#define EM_68HC05 72 /* Motorola MC68HC05 microcontroller */ +#define EM_SVX 73 /* Silicon Graphics SVx */ +#define EM_ST19 74 /* STMicroelectronics ST19 8 bit mc */ +#define EM_VAX 75 /* Digital VAX */ +#define EM_CRIS 76 /* Axis Communications 32-bit emb.proc */ +#define EM_JAVELIN 77 /* Infineon Technologies 32-bit emb.proc */ +#define EM_FIREPATH 78 /* Element 14 64-bit DSP Processor */ +#define EM_ZSP 79 /* LSI Logic 16-bit DSP Processor */ +#define EM_MMIX 80 /* Donald Knuth's educational 64-bit proc */ +#define EM_HUANY 81 /* Harvard University machine-independent object files */ +#define EM_PRISM 82 /* SiTera Prism */ +#define EM_AVR 83 /* Atmel AVR 8-bit microcontroller */ +#define EM_FR30 84 /* Fujitsu FR30 */ +#define EM_D10V 85 /* Mitsubishi D10V */ +#define EM_D30V 86 /* Mitsubishi D30V */ +#define EM_V850 87 /* NEC v850 */ +#define EM_M32R 88 /* Mitsubishi M32R */ +#define EM_MN10300 89 /* Matsushita MN10300 */ +#define EM_MN10200 90 /* Matsushita MN10200 */ +#define EM_PJ 91 /* picoJava */ +#define EM_OPENRISC 92 /* OpenRISC 32-bit embedded processor */ +#define EM_ARC_COMPACT 93 /* ARC International ARCompact */ +#define EM_XTENSA 94 /* Tensilica Xtensa Architecture */ +#define EM_VIDEOCORE 95 /* Alphamosaic VideoCore */ +#define EM_TMM_GPP 96 /* Thompson Multimedia General Purpose Proc */ +#define EM_NS32K 97 /* National Semi. 32000 */ +#define EM_TPC 98 /* Tenor Network TPC */ +#define EM_SNP1K 99 /* Trebia SNP 1000 */ +#define EM_ST200 100 /* STMicroelectronics ST200 */ +#define EM_IP2K 101 /* Ubicom IP2xxx */ +#define EM_MAX 102 /* MAX processor */ +#define EM_CR 103 /* National Semi. CompactRISC */ +#define EM_F2MC16 104 /* Fujitsu F2MC16 */ +#define EM_MSP430 105 /* Texas Instruments msp430 */ +#define EM_BLACKFIN 106 /* Analog Devices Blackfin DSP */ +#define EM_SE_C33 107 /* Seiko Epson S1C33 family */ +#define EM_SEP 108 /* Sharp embedded microprocessor */ +#define EM_ARCA 109 /* Arca RISC */ +#define EM_UNICORE 110 /* PKU-Unity & MPRC Peking Uni. mc series */ +#define EM_EXCESS 111 /* eXcess configurable cpu */ +#define EM_DXP 112 /* Icera Semi. Deep Execution Processor */ +#define EM_ALTERA_NIOS2 113 /* Altera Nios II */ +#define EM_CRX 114 /* National Semi. CompactRISC CRX */ +#define EM_XGATE 115 /* Motorola XGATE */ +#define EM_C166 116 /* Infineon C16x/XC16x */ +#define EM_M16C 117 /* Renesas M16C */ +#define EM_DSPIC30F 118 /* Microchip Technology dsPIC30F */ +#define EM_CE 119 /* Freescale Communication Engine RISC */ +#define EM_M32C 120 /* Renesas M32C */ + /* reserved 121-130 */ +#define EM_TSK3000 131 /* Altium TSK3000 */ +#define EM_RS08 132 /* Freescale RS08 */ +#define EM_SHARC 133 /* Analog Devices SHARC family */ +#define EM_ECOG2 134 /* Cyan Technology eCOG2 */ +#define EM_SCORE7 135 /* Sunplus S+core7 RISC */ +#define EM_DSP24 136 /* New Japan Radio (NJR) 24-bit DSP */ +#define EM_VIDEOCORE3 137 /* Broadcom VideoCore III */ +#define EM_LATTICEMICO32 138 /* RISC for Lattice FPGA */ +#define EM_SE_C17 139 /* Seiko Epson C17 */ +#define EM_TI_C6000 140 /* Texas Instruments TMS320C6000 DSP */ +#define EM_TI_C2000 141 /* Texas Instruments TMS320C2000 DSP */ +#define EM_TI_C5500 142 /* Texas Instruments TMS320C55x DSP */ +#define EM_TI_ARP32 143 /* Texas Instruments App. Specific RISC */ +#define EM_TI_PRU 144 /* Texas Instruments Prog. Realtime Unit */ + /* reserved 145-159 */ +#define EM_MMDSP_PLUS 160 /* STMicroelectronics 64bit VLIW DSP */ +#define EM_CYPRESS_M8C 161 /* Cypress M8C */ +#define EM_R32C 162 /* Renesas R32C */ +#define EM_TRIMEDIA 163 /* NXP Semi. TriMedia */ +#define EM_QDSP6 164 /* QUALCOMM DSP6 */ +#define EM_8051 165 /* Intel 8051 and variants */ +#define EM_STXP7X 166 /* STMicroelectronics STxP7x */ +#define EM_NDS32 167 /* Andes Tech. compact code emb. RISC */ +#define EM_ECOG1X 168 /* Cyan Technology eCOG1X */ +#define EM_MAXQ30 169 /* Dallas Semi. MAXQ30 mc */ +#define EM_XIMO16 170 /* New Japan Radio (NJR) 16-bit DSP */ +#define EM_MANIK 171 /* M2000 Reconfigurable RISC */ +#define EM_CRAYNV2 172 /* Cray NV2 vector architecture */ +#define EM_RX 173 /* Renesas RX */ +#define EM_METAG 174 /* Imagination Tech. META */ +#define EM_MCST_ELBRUS 175 /* MCST Elbrus */ +#define EM_ECOG16 176 /* Cyan Technology eCOG16 */ +#define EM_CR16 177 /* National Semi. CompactRISC CR16 */ +#define EM_ETPU 178 /* Freescale Extended Time Processing Unit */ +#define EM_SLE9X 179 /* Infineon Tech. SLE9X */ +#define EM_L10M 180 /* Intel L10M */ +#define EM_K10M 181 /* Intel K10M */ + /* reserved 182 */ +#define EM_AARCH64 183 /* ARM AARCH64 */ + /* reserved 184 */ +#define EM_AVR32 185 /* Amtel 32-bit microprocessor */ +#define EM_STM8 186 /* STMicroelectronics STM8 */ +#define EM_TILE64 187 /* Tileta TILE64 */ +#define EM_TILEPRO 188 /* Tilera TILEPro */ +#define EM_MICROBLAZE 189 /* Xilinx MicroBlaze */ +#define EM_CUDA 190 /* NVIDIA CUDA */ +#define EM_TILEGX 191 /* Tilera TILE-Gx */ +#define EM_CLOUDSHIELD 192 /* CloudShield */ +#define EM_COREA_1ST 193 /* KIPO-KAIST Core-A 1st gen. */ +#define EM_COREA_2ND 194 /* KIPO-KAIST Core-A 2nd gen. */ +#define EM_ARC_COMPACT2 195 /* Synopsys ARCompact V2 */ +#define EM_OPEN8 196 /* Open8 RISC */ +#define EM_RL78 197 /* Renesas RL78 */ +#define EM_VIDEOCORE5 198 /* Broadcom VideoCore V */ +#define EM_78KOR 199 /* Renesas 78KOR */ +#define EM_56800EX 200 /* Freescale 56800EX DSC */ +#define EM_BA1 201 /* Beyond BA1 */ +#define EM_BA2 202 /* Beyond BA2 */ +#define EM_XCORE 203 /* XMOS xCORE */ +#define EM_MCHP_PIC 204 /* Microchip 8-bit PIC(r) */ + /* reserved 205-209 */ +#define EM_KM32 210 /* KM211 KM32 */ +#define EM_KMX32 211 /* KM211 KMX32 */ +#define EM_EMX16 212 /* KM211 KMX16 */ +#define EM_EMX8 213 /* KM211 KMX8 */ +#define EM_KVARC 214 /* KM211 KVARC */ +#define EM_CDP 215 /* Paneve CDP */ +#define EM_COGE 216 /* Cognitive Smart Memory Processor */ +#define EM_COOL 217 /* Bluechip CoolEngine */ +#define EM_NORC 218 /* Nanoradio Optimized RISC */ +#define EM_CSR_KALIMBA 219 /* CSR Kalimba */ +#define EM_Z80 220 /* Zilog Z80 */ +#define EM_VISIUM 221 /* Controls and Data Services VISIUMcore */ +#define EM_FT32 222 /* FTDI Chip FT32 */ +#define EM_MOXIE 223 /* Moxie processor */ +#define EM_AMDGPU 224 /* AMD GPU */ + /* reserved 225-242 */ +#define EM_RISCV 243 /* RISC-V */ + +#define EM_BPF 247 /* Linux BPF -- in-kernel virtual machine */ +#define EM_CSKY 252 /* C_SKY */ + +#define EM_NUM 253 + +/* Old spellings/synonyms. */ + +#define EM_ARC_A5 EM_ARC_COMPACT + +/* If it is necessary to assign new unofficial EM_* values, please + pick large random numbers (0x8523, 0xa7f2, etc.) to minimize the + chances of collision with official or non-GNU unofficial values. */ + +#define EM_ALPHA 0x9026 + +/* Legal values for e_version (version). */ + +#define EV_NONE 0 /* Invalid ELF version */ +#define EV_CURRENT 1 /* Current version */ +#define EV_NUM 2 + +/* Section header. */ + +typedef struct +{ + Elf32_Word sh_name; /* Section name (string tbl index) */ + Elf32_Word sh_type; /* Section type */ + Elf32_Word sh_flags; /* Section flags */ + Elf32_Addr sh_addr; /* Section virtual addr at execution */ + Elf32_Off sh_offset; /* Section file offset */ + Elf32_Word sh_size; /* Section size in bytes */ + Elf32_Word sh_link; /* Link to another section */ + Elf32_Word sh_info; /* Additional section information */ + Elf32_Word sh_addralign; /* Section alignment */ + Elf32_Word sh_entsize; /* Entry size if section holds table */ +} Elf32_Shdr; + +typedef struct +{ + Elf64_Word sh_name; /* Section name (string tbl index) */ + Elf64_Word sh_type; /* Section type */ + Elf64_Xword sh_flags; /* Section flags */ + Elf64_Addr sh_addr; /* Section virtual addr at execution */ + Elf64_Off sh_offset; /* Section file offset */ + Elf64_Xword sh_size; /* Section size in bytes */ + Elf64_Word sh_link; /* Link to another section */ + Elf64_Word sh_info; /* Additional section information */ + Elf64_Xword sh_addralign; /* Section alignment */ + Elf64_Xword sh_entsize; /* Entry size if section holds table */ +} Elf64_Shdr; + +/* Special section indices. */ + +#define SHN_UNDEF 0 /* Undefined section */ +#define SHN_LORESERVE 0xff00 /* Start of reserved indices */ +#define SHN_LOPROC 0xff00 /* Start of processor-specific */ +#define SHN_BEFORE 0xff00 /* Order section before all others + (Solaris). */ +#define SHN_AFTER 0xff01 /* Order section after all others + (Solaris). */ +#define SHN_HIPROC 0xff1f /* End of processor-specific */ +#define SHN_LOOS 0xff20 /* Start of OS-specific */ +#define SHN_HIOS 0xff3f /* End of OS-specific */ +#define SHN_ABS 0xfff1 /* Associated symbol is absolute */ +#define SHN_COMMON 0xfff2 /* Associated symbol is common */ +#define SHN_XINDEX 0xffff /* Index is in extra table. */ +#define SHN_HIRESERVE 0xffff /* End of reserved indices */ + +/* Legal values for sh_type (section type). */ + +#define SHT_NULL 0 /* Section header table entry unused */ +#define SHT_PROGBITS 1 /* Program data */ +#define SHT_SYMTAB 2 /* Symbol table */ +#define SHT_STRTAB 3 /* String table */ +#define SHT_RELA 4 /* Relocation entries with addends */ +#define SHT_HASH 5 /* Symbol hash table */ +#define SHT_DYNAMIC 6 /* Dynamic linking information */ +#define SHT_NOTE 7 /* Notes */ +#define SHT_NOBITS 8 /* Program space with no data (bss) */ +#define SHT_REL 9 /* Relocation entries, no addends */ +#define SHT_SHLIB 10 /* Reserved */ +#define SHT_DYNSYM 11 /* Dynamic linker symbol table */ +#define SHT_INIT_ARRAY 14 /* Array of constructors */ +#define SHT_FINI_ARRAY 15 /* Array of destructors */ +#define SHT_PREINIT_ARRAY 16 /* Array of pre-constructors */ +#define SHT_GROUP 17 /* Section group */ +#define SHT_SYMTAB_SHNDX 18 /* Extended section indeces */ +#define SHT_NUM 19 /* Number of defined types. */ +#define SHT_LOOS 0x60000000 /* Start OS-specific. */ +#define SHT_GNU_ATTRIBUTES 0x6ffffff5 /* Object attributes. */ +#define SHT_GNU_HASH 0x6ffffff6 /* GNU-style hash table. */ +#define SHT_GNU_LIBLIST 0x6ffffff7 /* Prelink library list */ +#define SHT_CHECKSUM 0x6ffffff8 /* Checksum for DSO content. */ +#define SHT_LOSUNW 0x6ffffffa /* Sun-specific low bound. */ +#define SHT_SUNW_move 0x6ffffffa +#define SHT_SUNW_COMDAT 0x6ffffffb +#define SHT_SUNW_syminfo 0x6ffffffc +#define SHT_GNU_verdef 0x6ffffffd /* Version definition section. */ +#define SHT_GNU_verneed 0x6ffffffe /* Version needs section. */ +#define SHT_GNU_versym 0x6fffffff /* Version symbol table. */ +#define SHT_HISUNW 0x6fffffff /* Sun-specific high bound. */ +#define SHT_HIOS 0x6fffffff /* End OS-specific type */ +#define SHT_LOPROC 0x70000000 /* Start of processor-specific */ +#define SHT_HIPROC 0x7fffffff /* End of processor-specific */ +#define SHT_LOUSER 0x80000000 /* Start of application-specific */ +#define SHT_HIUSER 0x8fffffff /* End of application-specific */ + +/* Legal values for sh_flags (section flags). */ + +#define SHF_WRITE (1 << 0) /* Writable */ +#define SHF_ALLOC (1 << 1) /* Occupies memory during execution */ +#define SHF_EXECINSTR (1 << 2) /* Executable */ +#define SHF_MERGE (1 << 4) /* Might be merged */ +#define SHF_STRINGS (1 << 5) /* Contains nul-terminated strings */ +#define SHF_INFO_LINK (1 << 6) /* `sh_info' contains SHT index */ +#define SHF_LINK_ORDER (1 << 7) /* Preserve order after combining */ +#define SHF_OS_NONCONFORMING (1 << 8) /* Non-standard OS specific handling + required */ +#define SHF_GROUP (1 << 9) /* Section is member of a group. */ +#define SHF_TLS (1 << 10) /* Section hold thread-local data. */ +#define SHF_COMPRESSED (1 << 11) /* Section with compressed data. */ +#define SHF_MASKOS 0x0ff00000 /* OS-specific. */ +#define SHF_MASKPROC 0xf0000000 /* Processor-specific */ +#define SHF_ORDERED (1 << 30) /* Special ordering requirement + (Solaris). */ +#define SHF_EXCLUDE (1U << 31) /* Section is excluded unless + referenced or allocated (Solaris).*/ + +/* Section compression header. Used when SHF_COMPRESSED is set. */ + +typedef struct +{ + Elf32_Word ch_type; /* Compression format. */ + Elf32_Word ch_size; /* Uncompressed data size. */ + Elf32_Word ch_addralign; /* Uncompressed data alignment. */ +} Elf32_Chdr; + +typedef struct +{ + Elf64_Word ch_type; /* Compression format. */ + Elf64_Word ch_reserved; + Elf64_Xword ch_size; /* Uncompressed data size. */ + Elf64_Xword ch_addralign; /* Uncompressed data alignment. */ +} Elf64_Chdr; + +/* Legal values for ch_type (compression algorithm). */ +#define ELFCOMPRESS_ZLIB 1 /* ZLIB/DEFLATE algorithm. */ +#define ELFCOMPRESS_LOOS 0x60000000 /* Start of OS-specific. */ +#define ELFCOMPRESS_HIOS 0x6fffffff /* End of OS-specific. */ +#define ELFCOMPRESS_LOPROC 0x70000000 /* Start of processor-specific. */ +#define ELFCOMPRESS_HIPROC 0x7fffffff /* End of processor-specific. */ + +/* Section group handling. */ +#define GRP_COMDAT 0x1 /* Mark group as COMDAT. */ + +/* Symbol table entry. */ + +typedef struct +{ + Elf32_Word st_name; /* Symbol name (string tbl index) */ + Elf32_Addr st_value; /* Symbol value */ + Elf32_Word st_size; /* Symbol size */ + unsigned char st_info; /* Symbol type and binding */ + unsigned char st_other; /* Symbol visibility */ + Elf32_Section st_shndx; /* Section index */ +} Elf32_Sym; + +typedef struct +{ + Elf64_Word st_name; /* Symbol name (string tbl index) */ + unsigned char st_info; /* Symbol type and binding */ + unsigned char st_other; /* Symbol visibility */ + Elf64_Section st_shndx; /* Section index */ + Elf64_Addr st_value; /* Symbol value */ + Elf64_Xword st_size; /* Symbol size */ +} Elf64_Sym; + +/* The syminfo section if available contains additional information about + every dynamic symbol. */ + +typedef struct +{ + Elf32_Half si_boundto; /* Direct bindings, symbol bound to */ + Elf32_Half si_flags; /* Per symbol flags */ +} Elf32_Syminfo; + +typedef struct +{ + Elf64_Half si_boundto; /* Direct bindings, symbol bound to */ + Elf64_Half si_flags; /* Per symbol flags */ +} Elf64_Syminfo; + +/* Possible values for si_boundto. */ +#define SYMINFO_BT_SELF 0xffff /* Symbol bound to self */ +#define SYMINFO_BT_PARENT 0xfffe /* Symbol bound to parent */ +#define SYMINFO_BT_LOWRESERVE 0xff00 /* Beginning of reserved entries */ + +/* Possible bitmasks for si_flags. */ +#define SYMINFO_FLG_DIRECT 0x0001 /* Direct bound symbol */ +#define SYMINFO_FLG_PASSTHRU 0x0002 /* Pass-thru symbol for translator */ +#define SYMINFO_FLG_COPY 0x0004 /* Symbol is a copy-reloc */ +#define SYMINFO_FLG_LAZYLOAD 0x0008 /* Symbol bound to object to be lazy + loaded */ +/* Syminfo version values. */ +#define SYMINFO_NONE 0 +#define SYMINFO_CURRENT 1 +#define SYMINFO_NUM 2 + + +/* How to extract and insert information held in the st_info field. */ + +#define ELF32_ST_BIND(val) (((unsigned char) (val)) >> 4) +#define ELF32_ST_TYPE(val) ((val) & 0xf) +#define ELF32_ST_INFO(bind, type) (((bind) << 4) + ((type) & 0xf)) + +/* Both Elf32_Sym and Elf64_Sym use the same one-byte st_info field. */ +#define ELF64_ST_BIND(val) ELF32_ST_BIND (val) +#define ELF64_ST_TYPE(val) ELF32_ST_TYPE (val) +#define ELF64_ST_INFO(bind, type) ELF32_ST_INFO ((bind), (type)) + +/* Legal values for ST_BIND subfield of st_info (symbol binding). */ + +#define STB_LOCAL 0 /* Local symbol */ +#define STB_GLOBAL 1 /* Global symbol */ +#define STB_WEAK 2 /* Weak symbol */ +#define STB_NUM 3 /* Number of defined types. */ +#define STB_LOOS 10 /* Start of OS-specific */ +#define STB_GNU_UNIQUE 10 /* Unique symbol. */ +#define STB_HIOS 12 /* End of OS-specific */ +#define STB_LOPROC 13 /* Start of processor-specific */ +#define STB_HIPROC 15 /* End of processor-specific */ + +/* Legal values for ST_TYPE subfield of st_info (symbol type). */ + +#define STT_NOTYPE 0 /* Symbol type is unspecified */ +#define STT_OBJECT 1 /* Symbol is a data object */ +#define STT_FUNC 2 /* Symbol is a code object */ +#define STT_SECTION 3 /* Symbol associated with a section */ +#define STT_FILE 4 /* Symbol's name is file name */ +#define STT_COMMON 5 /* Symbol is a common data object */ +#define STT_TLS 6 /* Symbol is thread-local data object*/ +#define STT_NUM 7 /* Number of defined types. */ +#define STT_LOOS 10 /* Start of OS-specific */ +#define STT_GNU_IFUNC 10 /* Symbol is indirect code object */ +#define STT_HIOS 12 /* End of OS-specific */ +#define STT_LOPROC 13 /* Start of processor-specific */ +#define STT_HIPROC 15 /* End of processor-specific */ + + +/* Symbol table indices are found in the hash buckets and chain table + of a symbol hash table section. This special index value indicates + the end of a chain, meaning no further symbols are found in that bucket. */ + +#define STN_UNDEF 0 /* End of a chain. */ + + +/* How to extract and insert information held in the st_other field. */ + +#define ELF32_ST_VISIBILITY(o) ((o) & 0x03) + +/* For ELF64 the definitions are the same. */ +#define ELF64_ST_VISIBILITY(o) ELF32_ST_VISIBILITY (o) + +/* Symbol visibility specification encoded in the st_other field. */ +#define STV_DEFAULT 0 /* Default symbol visibility rules */ +#define STV_INTERNAL 1 /* Processor specific hidden class */ +#define STV_HIDDEN 2 /* Sym unavailable in other modules */ +#define STV_PROTECTED 3 /* Not preemptible, not exported */ + + +/* Relocation table entry without addend (in section of type SHT_REL). */ + +typedef struct +{ + Elf32_Addr r_offset; /* Address */ + Elf32_Word r_info; /* Relocation type and symbol index */ +} Elf32_Rel; + +/* I have seen two different definitions of the Elf64_Rel and + Elf64_Rela structures, so we'll leave them out until Novell (or + whoever) gets their act together. */ +/* The following, at least, is used on Sparc v9, MIPS, and Alpha. */ + +typedef struct +{ + Elf64_Addr r_offset; /* Address */ + Elf64_Xword r_info; /* Relocation type and symbol index */ +} Elf64_Rel; + +/* Relocation table entry with addend (in section of type SHT_RELA). */ + +typedef struct +{ + Elf32_Addr r_offset; /* Address */ + Elf32_Word r_info; /* Relocation type and symbol index */ + Elf32_Sword r_addend; /* Addend */ +} Elf32_Rela; + +typedef struct +{ + Elf64_Addr r_offset; /* Address */ + Elf64_Xword r_info; /* Relocation type and symbol index */ + Elf64_Sxword r_addend; /* Addend */ +} Elf64_Rela; + +/* How to extract and insert information held in the r_info field. */ + +#define ELF32_R_SYM(val) ((val) >> 8) +#define ELF32_R_TYPE(val) ((val) & 0xff) +#define ELF32_R_INFO(sym, type) (((sym) << 8) + ((type) & 0xff)) + +#define ELF64_R_SYM(i) ((i) >> 32) +#define ELF64_R_TYPE(i) ((i) & 0xffffffff) +#define ELF64_R_INFO(sym,type) ((((Elf64_Xword) (sym)) << 32) + (type)) + +/* Program segment header. */ + +typedef struct +{ + Elf32_Word p_type; /* Segment type */ + Elf32_Off p_offset; /* Segment file offset */ + Elf32_Addr p_vaddr; /* Segment virtual address */ + Elf32_Addr p_paddr; /* Segment physical address */ + Elf32_Word p_filesz; /* Segment size in file */ + Elf32_Word p_memsz; /* Segment size in memory */ + Elf32_Word p_flags; /* Segment flags */ + Elf32_Word p_align; /* Segment alignment */ +} Elf32_Phdr; + +typedef struct +{ + Elf64_Word p_type; /* Segment type */ + Elf64_Word p_flags; /* Segment flags */ + Elf64_Off p_offset; /* Segment file offset */ + Elf64_Addr p_vaddr; /* Segment virtual address */ + Elf64_Addr p_paddr; /* Segment physical address */ + Elf64_Xword p_filesz; /* Segment size in file */ + Elf64_Xword p_memsz; /* Segment size in memory */ + Elf64_Xword p_align; /* Segment alignment */ +} Elf64_Phdr; + +/* Special value for e_phnum. This indicates that the real number of + program headers is too large to fit into e_phnum. Instead the real + value is in the field sh_info of section 0. */ + +#define PN_XNUM 0xffff + +/* Legal values for p_type (segment type). */ + +#define PT_NULL 0 /* Program header table entry unused */ +#define PT_LOAD 1 /* Loadable program segment */ +#define PT_DYNAMIC 2 /* Dynamic linking information */ +#define PT_INTERP 3 /* Program interpreter */ +#define PT_NOTE 4 /* Auxiliary information */ +#define PT_SHLIB 5 /* Reserved */ +#define PT_PHDR 6 /* Entry for header table itself */ +#define PT_TLS 7 /* Thread-local storage segment */ +#define PT_NUM 8 /* Number of defined types */ +#define PT_LOOS 0x60000000 /* Start of OS-specific */ +#define PT_GNU_EH_FRAME 0x6474e550 /* GCC .eh_frame_hdr segment */ +#define PT_GNU_STACK 0x6474e551 /* Indicates stack executability */ +#define PT_GNU_RELRO 0x6474e552 /* Read-only after relocation */ +#define PT_LOSUNW 0x6ffffffa +#define PT_SUNWBSS 0x6ffffffa /* Sun Specific segment */ +#define PT_SUNWSTACK 0x6ffffffb /* Stack segment */ +#define PT_HISUNW 0x6fffffff +#define PT_HIOS 0x6fffffff /* End of OS-specific */ +#define PT_LOPROC 0x70000000 /* Start of processor-specific */ +#define PT_HIPROC 0x7fffffff /* End of processor-specific */ + +/* Legal values for p_flags (segment flags). */ + +#define PF_X (1 << 0) /* Segment is executable */ +#define PF_W (1 << 1) /* Segment is writable */ +#define PF_R (1 << 2) /* Segment is readable */ +#define PF_MASKOS 0x0ff00000 /* OS-specific */ +#define PF_MASKPROC 0xf0000000 /* Processor-specific */ + +/* Legal values for note segment descriptor types for core files. */ + +#define NT_PRSTATUS 1 /* Contains copy of prstatus struct */ +#define NT_PRFPREG 2 /* Contains copy of fpregset + struct. */ +#define NT_FPREGSET 2 /* Contains copy of fpregset struct */ +#define NT_PRPSINFO 3 /* Contains copy of prpsinfo struct */ +#define NT_PRXREG 4 /* Contains copy of prxregset struct */ +#define NT_TASKSTRUCT 4 /* Contains copy of task structure */ +#define NT_PLATFORM 5 /* String from sysinfo(SI_PLATFORM) */ +#define NT_AUXV 6 /* Contains copy of auxv array */ +#define NT_GWINDOWS 7 /* Contains copy of gwindows struct */ +#define NT_ASRS 8 /* Contains copy of asrset struct */ +#define NT_PSTATUS 10 /* Contains copy of pstatus struct */ +#define NT_PSINFO 13 /* Contains copy of psinfo struct */ +#define NT_PRCRED 14 /* Contains copy of prcred struct */ +#define NT_UTSNAME 15 /* Contains copy of utsname struct */ +#define NT_LWPSTATUS 16 /* Contains copy of lwpstatus struct */ +#define NT_LWPSINFO 17 /* Contains copy of lwpinfo struct */ +#define NT_PRFPXREG 20 /* Contains copy of fprxregset struct */ +#define NT_SIGINFO 0x53494749 /* Contains copy of siginfo_t, + size might increase */ +#define NT_FILE 0x46494c45 /* Contains information about mapped + files */ +#define NT_PRXFPREG 0x46e62b7f /* Contains copy of user_fxsr_struct */ +#define NT_PPC_VMX 0x100 /* PowerPC Altivec/VMX registers */ +#define NT_PPC_SPE 0x101 /* PowerPC SPE/EVR registers */ +#define NT_PPC_VSX 0x102 /* PowerPC VSX registers */ +#define NT_PPC_TAR 0x103 /* Target Address Register */ +#define NT_PPC_PPR 0x104 /* Program Priority Register */ +#define NT_PPC_DSCR 0x105 /* Data Stream Control Register */ +#define NT_PPC_EBB 0x106 /* Event Based Branch Registers */ +#define NT_PPC_PMU 0x107 /* Performance Monitor Registers */ +#define NT_PPC_TM_CGPR 0x108 /* TM checkpointed GPR Registers */ +#define NT_PPC_TM_CFPR 0x109 /* TM checkpointed FPR Registers */ +#define NT_PPC_TM_CVMX 0x10a /* TM checkpointed VMX Registers */ +#define NT_PPC_TM_CVSX 0x10b /* TM checkpointed VSX Registers */ +#define NT_PPC_TM_SPR 0x10c /* TM Special Purpose Registers */ +#define NT_PPC_TM_CTAR 0x10d /* TM checkpointed Target Address + Register */ +#define NT_PPC_TM_CPPR 0x10e /* TM checkpointed Program Priority + Register */ +#define NT_PPC_TM_CDSCR 0x10f /* TM checkpointed Data Stream Control + Register */ +#define NT_PPC_PKEY 0x110 /* Memory Protection Keys + registers. */ +#define NT_386_TLS 0x200 /* i386 TLS slots (struct user_desc) */ +#define NT_386_IOPERM 0x201 /* x86 io permission bitmap (1=deny) */ +#define NT_X86_XSTATE 0x202 /* x86 extended state using xsave */ +#define NT_S390_HIGH_GPRS 0x300 /* s390 upper register halves */ +#define NT_S390_TIMER 0x301 /* s390 timer register */ +#define NT_S390_TODCMP 0x302 /* s390 TOD clock comparator register */ +#define NT_S390_TODPREG 0x303 /* s390 TOD programmable register */ +#define NT_S390_CTRS 0x304 /* s390 control registers */ +#define NT_S390_PREFIX 0x305 /* s390 prefix register */ +#define NT_S390_LAST_BREAK 0x306 /* s390 breaking event address */ +#define NT_S390_SYSTEM_CALL 0x307 /* s390 system call restart data */ +#define NT_S390_TDB 0x308 /* s390 transaction diagnostic block */ +#define NT_S390_VXRS_LOW 0x309 /* s390 vector registers 0-15 + upper half. */ +#define NT_S390_VXRS_HIGH 0x30a /* s390 vector registers 16-31. */ +#define NT_S390_GS_CB 0x30b /* s390 guarded storage registers. */ +#define NT_S390_GS_BC 0x30c /* s390 guarded storage + broadcast control block. */ +#define NT_S390_RI_CB 0x30d /* s390 runtime instrumentation. */ +#define NT_ARM_VFP 0x400 /* ARM VFP/NEON registers */ +#define NT_ARM_TLS 0x401 /* ARM TLS register */ +#define NT_ARM_HW_BREAK 0x402 /* ARM hardware breakpoint registers */ +#define NT_ARM_HW_WATCH 0x403 /* ARM hardware watchpoint registers */ +#define NT_ARM_SYSTEM_CALL 0x404 /* ARM system call number */ +#define NT_ARM_SVE 0x405 /* ARM Scalable Vector Extension + registers */ +#define NT_VMCOREDD 0x700 /* Vmcore Device Dump Note. */ +#define NT_MIPS_DSP 0x800 /* MIPS DSP ASE registers. */ +#define NT_MIPS_FP_MODE 0x801 /* MIPS floating-point mode. */ + +/* Legal values for the note segment descriptor types for object files. */ + +#define NT_VERSION 1 /* Contains a version string. */ + + +/* Dynamic section entry. */ + +typedef struct +{ + Elf32_Sword d_tag; /* Dynamic entry type */ + union + { + Elf32_Word d_val; /* Integer value */ + Elf32_Addr d_ptr; /* Address value */ + } d_un; +} Elf32_Dyn; + +typedef struct +{ + Elf64_Sxword d_tag; /* Dynamic entry type */ + union + { + Elf64_Xword d_val; /* Integer value */ + Elf64_Addr d_ptr; /* Address value */ + } d_un; +} Elf64_Dyn; + +/* Legal values for d_tag (dynamic entry type). */ + +#define DT_NULL 0 /* Marks end of dynamic section */ +#define DT_NEEDED 1 /* Name of needed library */ +#define DT_PLTRELSZ 2 /* Size in bytes of PLT relocs */ +#define DT_PLTGOT 3 /* Processor defined value */ +#define DT_HASH 4 /* Address of symbol hash table */ +#define DT_STRTAB 5 /* Address of string table */ +#define DT_SYMTAB 6 /* Address of symbol table */ +#define DT_RELA 7 /* Address of Rela relocs */ +#define DT_RELASZ 8 /* Total size of Rela relocs */ +#define DT_RELAENT 9 /* Size of one Rela reloc */ +#define DT_STRSZ 10 /* Size of string table */ +#define DT_SYMENT 11 /* Size of one symbol table entry */ +#define DT_INIT 12 /* Address of init function */ +#define DT_FINI 13 /* Address of termination function */ +#define DT_SONAME 14 /* Name of shared object */ +#define DT_RPATH 15 /* Library search path (deprecated) */ +#define DT_SYMBOLIC 16 /* Start symbol search here */ +#define DT_REL 17 /* Address of Rel relocs */ +#define DT_RELSZ 18 /* Total size of Rel relocs */ +#define DT_RELENT 19 /* Size of one Rel reloc */ +#define DT_PLTREL 20 /* Type of reloc in PLT */ +#define DT_DEBUG 21 /* For debugging; unspecified */ +#define DT_TEXTREL 22 /* Reloc might modify .text */ +#define DT_JMPREL 23 /* Address of PLT relocs */ +#define DT_BIND_NOW 24 /* Process relocations of object */ +#define DT_INIT_ARRAY 25 /* Array with addresses of init fct */ +#define DT_FINI_ARRAY 26 /* Array with addresses of fini fct */ +#define DT_INIT_ARRAYSZ 27 /* Size in bytes of DT_INIT_ARRAY */ +#define DT_FINI_ARRAYSZ 28 /* Size in bytes of DT_FINI_ARRAY */ +#define DT_RUNPATH 29 /* Library search path */ +#define DT_FLAGS 30 /* Flags for the object being loaded */ +#define DT_ENCODING 32 /* Start of encoded range */ +#define DT_PREINIT_ARRAY 32 /* Array with addresses of preinit fct*/ +#define DT_PREINIT_ARRAYSZ 33 /* size in bytes of DT_PREINIT_ARRAY */ +#define DT_SYMTAB_SHNDX 34 /* Address of SYMTAB_SHNDX section */ +#define DT_NUM 35 /* Number used */ +#define DT_LOOS 0x6000000d /* Start of OS-specific */ +#define DT_HIOS 0x6ffff000 /* End of OS-specific */ +#define DT_LOPROC 0x70000000 /* Start of processor-specific */ +#define DT_HIPROC 0x7fffffff /* End of processor-specific */ +#define DT_PROCNUM DT_MIPS_NUM /* Most used by any processor */ + +/* DT_* entries which fall between DT_VALRNGHI & DT_VALRNGLO use the + Dyn.d_un.d_val field of the Elf*_Dyn structure. This follows Sun's + approach. */ +#define DT_VALRNGLO 0x6ffffd00 +#define DT_GNU_PRELINKED 0x6ffffdf5 /* Prelinking timestamp */ +#define DT_GNU_CONFLICTSZ 0x6ffffdf6 /* Size of conflict section */ +#define DT_GNU_LIBLISTSZ 0x6ffffdf7 /* Size of library list */ +#define DT_CHECKSUM 0x6ffffdf8 +#define DT_PLTPADSZ 0x6ffffdf9 +#define DT_MOVEENT 0x6ffffdfa +#define DT_MOVESZ 0x6ffffdfb +#define DT_FEATURE_1 0x6ffffdfc /* Feature selection (DTF_*). */ +#define DT_POSFLAG_1 0x6ffffdfd /* Flags for DT_* entries, effecting + the following DT_* entry. */ +#define DT_SYMINSZ 0x6ffffdfe /* Size of syminfo table (in bytes) */ +#define DT_SYMINENT 0x6ffffdff /* Entry size of syminfo */ +#define DT_VALRNGHI 0x6ffffdff +#define DT_VALTAGIDX(tag) (DT_VALRNGHI - (tag)) /* Reverse order! */ +#define DT_VALNUM 12 + +/* DT_* entries which fall between DT_ADDRRNGHI & DT_ADDRRNGLO use the + Dyn.d_un.d_ptr field of the Elf*_Dyn structure. + + If any adjustment is made to the ELF object after it has been + built these entries will need to be adjusted. */ +#define DT_ADDRRNGLO 0x6ffffe00 +#define DT_GNU_HASH 0x6ffffef5 /* GNU-style hash table. */ +#define DT_TLSDESC_PLT 0x6ffffef6 +#define DT_TLSDESC_GOT 0x6ffffef7 +#define DT_GNU_CONFLICT 0x6ffffef8 /* Start of conflict section */ +#define DT_GNU_LIBLIST 0x6ffffef9 /* Library list */ +#define DT_CONFIG 0x6ffffefa /* Configuration information. */ +#define DT_DEPAUDIT 0x6ffffefb /* Dependency auditing. */ +#define DT_AUDIT 0x6ffffefc /* Object auditing. */ +#define DT_PLTPAD 0x6ffffefd /* PLT padding. */ +#define DT_MOVETAB 0x6ffffefe /* Move table. */ +#define DT_SYMINFO 0x6ffffeff /* Syminfo table. */ +#define DT_ADDRRNGHI 0x6ffffeff +#define DT_ADDRTAGIDX(tag) (DT_ADDRRNGHI - (tag)) /* Reverse order! */ +#define DT_ADDRNUM 11 + +/* The versioning entry types. The next are defined as part of the + GNU extension. */ +#define DT_VERSYM 0x6ffffff0 + +#define DT_RELACOUNT 0x6ffffff9 +#define DT_RELCOUNT 0x6ffffffa + +/* These were chosen by Sun. */ +#define DT_FLAGS_1 0x6ffffffb /* State flags, see DF_1_* below. */ +#define DT_VERDEF 0x6ffffffc /* Address of version definition + table */ +#define DT_VERDEFNUM 0x6ffffffd /* Number of version definitions */ +#define DT_VERNEED 0x6ffffffe /* Address of table with needed + versions */ +#define DT_VERNEEDNUM 0x6fffffff /* Number of needed versions */ +#define DT_VERSIONTAGIDX(tag) (DT_VERNEEDNUM - (tag)) /* Reverse order! */ +#define DT_VERSIONTAGNUM 16 + +/* Sun added these machine-independent extensions in the "processor-specific" + range. Be compatible. */ +#define DT_AUXILIARY 0x7ffffffd /* Shared object to load before self */ +#define DT_FILTER 0x7fffffff /* Shared object to get values from */ +#define DT_EXTRATAGIDX(tag) ((Elf32_Word)-((Elf32_Sword) (tag) <<1>>1)-1) +#define DT_EXTRANUM 3 + +/* Values of `d_un.d_val' in the DT_FLAGS entry. */ +#define DF_ORIGIN 0x00000001 /* Object may use DF_ORIGIN */ +#define DF_SYMBOLIC 0x00000002 /* Symbol resolutions starts here */ +#define DF_TEXTREL 0x00000004 /* Object contains text relocations */ +#define DF_BIND_NOW 0x00000008 /* No lazy binding for this object */ +#define DF_STATIC_TLS 0x00000010 /* Module uses the static TLS model */ + +/* State flags selectable in the `d_un.d_val' element of the DT_FLAGS_1 + entry in the dynamic section. */ +#define DF_1_NOW 0x00000001 /* Set RTLD_NOW for this object. */ +#define DF_1_GLOBAL 0x00000002 /* Set RTLD_GLOBAL for this object. */ +#define DF_1_GROUP 0x00000004 /* Set RTLD_GROUP for this object. */ +#define DF_1_NODELETE 0x00000008 /* Set RTLD_NODELETE for this object.*/ +#define DF_1_LOADFLTR 0x00000010 /* Trigger filtee loading at runtime.*/ +#define DF_1_INITFIRST 0x00000020 /* Set RTLD_INITFIRST for this object*/ +#define DF_1_NOOPEN 0x00000040 /* Set RTLD_NOOPEN for this object. */ +#define DF_1_ORIGIN 0x00000080 /* $ORIGIN must be handled. */ +#define DF_1_DIRECT 0x00000100 /* Direct binding enabled. */ +#define DF_1_TRANS 0x00000200 +#define DF_1_INTERPOSE 0x00000400 /* Object is used to interpose. */ +#define DF_1_NODEFLIB 0x00000800 /* Ignore default lib search path. */ +#define DF_1_NODUMP 0x00001000 /* Object can't be dldump'ed. */ +#define DF_1_CONFALT 0x00002000 /* Configuration alternative created.*/ +#define DF_1_ENDFILTEE 0x00004000 /* Filtee terminates filters search. */ +#define DF_1_DISPRELDNE 0x00008000 /* Disp reloc applied at build time. */ +#define DF_1_DISPRELPND 0x00010000 /* Disp reloc applied at run-time. */ +#define DF_1_NODIRECT 0x00020000 /* Object has no-direct binding. */ +#define DF_1_IGNMULDEF 0x00040000 +#define DF_1_NOKSYMS 0x00080000 +#define DF_1_NOHDR 0x00100000 +#define DF_1_EDITED 0x00200000 /* Object is modified after built. */ +#define DF_1_NORELOC 0x00400000 +#define DF_1_SYMINTPOSE 0x00800000 /* Object has individual interposers. */ +#define DF_1_GLOBAUDIT 0x01000000 /* Global auditing required. */ +#define DF_1_SINGLETON 0x02000000 /* Singleton symbols are used. */ +#define DF_1_STUB 0x04000000 +#define DF_1_PIE 0x08000000 + +/* Flags for the feature selection in DT_FEATURE_1. */ +#define DTF_1_PARINIT 0x00000001 +#define DTF_1_CONFEXP 0x00000002 + +/* Flags in the DT_POSFLAG_1 entry effecting only the next DT_* entry. */ +#define DF_P1_LAZYLOAD 0x00000001 /* Lazyload following object. */ +#define DF_P1_GROUPPERM 0x00000002 /* Symbols from next object are not + generally available. */ + +/* Version definition sections. */ + +typedef struct +{ + Elf32_Half vd_version; /* Version revision */ + Elf32_Half vd_flags; /* Version information */ + Elf32_Half vd_ndx; /* Version Index */ + Elf32_Half vd_cnt; /* Number of associated aux entries */ + Elf32_Word vd_hash; /* Version name hash value */ + Elf32_Word vd_aux; /* Offset in bytes to verdaux array */ + Elf32_Word vd_next; /* Offset in bytes to next verdef + entry */ +} Elf32_Verdef; + +typedef struct +{ + Elf64_Half vd_version; /* Version revision */ + Elf64_Half vd_flags; /* Version information */ + Elf64_Half vd_ndx; /* Version Index */ + Elf64_Half vd_cnt; /* Number of associated aux entries */ + Elf64_Word vd_hash; /* Version name hash value */ + Elf64_Word vd_aux; /* Offset in bytes to verdaux array */ + Elf64_Word vd_next; /* Offset in bytes to next verdef + entry */ +} Elf64_Verdef; + + +/* Legal values for vd_version (version revision). */ +#define VER_DEF_NONE 0 /* No version */ +#define VER_DEF_CURRENT 1 /* Current version */ +#define VER_DEF_NUM 2 /* Given version number */ + +/* Legal values for vd_flags (version information flags). */ +#define VER_FLG_BASE 0x1 /* Version definition of file itself */ +#define VER_FLG_WEAK 0x2 /* Weak version identifier */ + +/* Versym symbol index values. */ +#define VER_NDX_LOCAL 0 /* Symbol is local. */ +#define VER_NDX_GLOBAL 1 /* Symbol is global. */ +#define VER_NDX_LORESERVE 0xff00 /* Beginning of reserved entries. */ +#define VER_NDX_ELIMINATE 0xff01 /* Symbol is to be eliminated. */ + +/* Auxialiary version information. */ + +typedef struct +{ + Elf32_Word vda_name; /* Version or dependency names */ + Elf32_Word vda_next; /* Offset in bytes to next verdaux + entry */ +} Elf32_Verdaux; + +typedef struct +{ + Elf64_Word vda_name; /* Version or dependency names */ + Elf64_Word vda_next; /* Offset in bytes to next verdaux + entry */ +} Elf64_Verdaux; + + +/* Version dependency section. */ + +typedef struct +{ + Elf32_Half vn_version; /* Version of structure */ + Elf32_Half vn_cnt; /* Number of associated aux entries */ + Elf32_Word vn_file; /* Offset of filename for this + dependency */ + Elf32_Word vn_aux; /* Offset in bytes to vernaux array */ + Elf32_Word vn_next; /* Offset in bytes to next verneed + entry */ +} Elf32_Verneed; + +typedef struct +{ + Elf64_Half vn_version; /* Version of structure */ + Elf64_Half vn_cnt; /* Number of associated aux entries */ + Elf64_Word vn_file; /* Offset of filename for this + dependency */ + Elf64_Word vn_aux; /* Offset in bytes to vernaux array */ + Elf64_Word vn_next; /* Offset in bytes to next verneed + entry */ +} Elf64_Verneed; + + +/* Legal values for vn_version (version revision). */ +#define VER_NEED_NONE 0 /* No version */ +#define VER_NEED_CURRENT 1 /* Current version */ +#define VER_NEED_NUM 2 /* Given version number */ + +/* Auxiliary needed version information. */ + +typedef struct +{ + Elf32_Word vna_hash; /* Hash value of dependency name */ + Elf32_Half vna_flags; /* Dependency specific information */ + Elf32_Half vna_other; /* Unused */ + Elf32_Word vna_name; /* Dependency name string offset */ + Elf32_Word vna_next; /* Offset in bytes to next vernaux + entry */ +} Elf32_Vernaux; + +typedef struct +{ + Elf64_Word vna_hash; /* Hash value of dependency name */ + Elf64_Half vna_flags; /* Dependency specific information */ + Elf64_Half vna_other; /* Unused */ + Elf64_Word vna_name; /* Dependency name string offset */ + Elf64_Word vna_next; /* Offset in bytes to next vernaux + entry */ +} Elf64_Vernaux; + + +/* Legal values for vna_flags. */ +#define VER_FLG_WEAK 0x2 /* Weak version identifier */ + + +/* Auxiliary vector. */ + +/* This vector is normally only used by the program interpreter. The + usual definition in an ABI supplement uses the name auxv_t. The + vector is not usually defined in a standard file, but it + can't hurt. We rename it to avoid conflicts. The sizes of these + types are an arrangement between the exec server and the program + interpreter, so we don't fully specify them here. */ + +typedef struct +{ + uint32_t a_type; /* Entry type */ + union + { + uint32_t a_val; /* Integer value */ + /* We use to have pointer elements added here. We cannot do that, + though, since it does not work when using 32-bit definitions + on 64-bit platforms and vice versa. */ + } a_un; +} Elf32_auxv_t; + +typedef struct +{ + uint64_t a_type; /* Entry type */ + union + { + uint64_t a_val; /* Integer value */ + /* We use to have pointer elements added here. We cannot do that, + though, since it does not work when using 32-bit definitions + on 64-bit platforms and vice versa. */ + } a_un; +} Elf64_auxv_t; + +/* Legal values for a_type (entry type). */ + +#define AT_NULL 0 /* End of vector */ +#define AT_IGNORE 1 /* Entry should be ignored */ +#define AT_EXECFD 2 /* File descriptor of program */ +#define AT_PHDR 3 /* Program headers for program */ +#define AT_PHENT 4 /* Size of program header entry */ +#define AT_PHNUM 5 /* Number of program headers */ +#define AT_PAGESZ 6 /* System page size */ +#define AT_BASE 7 /* Base address of interpreter */ +#define AT_FLAGS 8 /* Flags */ +#define AT_ENTRY 9 /* Entry point of program */ +#define AT_NOTELF 10 /* Program is not ELF */ +#define AT_UID 11 /* Real uid */ +#define AT_EUID 12 /* Effective uid */ +#define AT_GID 13 /* Real gid */ +#define AT_EGID 14 /* Effective gid */ +#define AT_CLKTCK 17 /* Frequency of times() */ + +/* Some more special a_type values describing the hardware. */ +#define AT_PLATFORM 15 /* String identifying platform. */ +#define AT_HWCAP 16 /* Machine-dependent hints about + processor capabilities. */ + +/* This entry gives some information about the FPU initialization + performed by the kernel. */ +#define AT_FPUCW 18 /* Used FPU control word. */ + +/* Cache block sizes. */ +#define AT_DCACHEBSIZE 19 /* Data cache block size. */ +#define AT_ICACHEBSIZE 20 /* Instruction cache block size. */ +#define AT_UCACHEBSIZE 21 /* Unified cache block size. */ + +/* A special ignored value for PPC, used by the kernel to control the + interpretation of the AUXV. Must be > 16. */ +#define AT_IGNOREPPC 22 /* Entry should be ignored. */ + +#define AT_SECURE 23 /* Boolean, was exec setuid-like? */ + +#define AT_BASE_PLATFORM 24 /* String identifying real platforms.*/ + +#define AT_RANDOM 25 /* Address of 16 random bytes. */ + +#define AT_HWCAP2 26 /* More machine-dependent hints about + processor capabilities. */ + +#define AT_EXECFN 31 /* Filename of executable. */ + +/* Pointer to the global system page used for system calls and other + nice things. */ +#define AT_SYSINFO 32 +#define AT_SYSINFO_EHDR 33 + +/* Shapes of the caches. Bits 0-3 contains associativity; bits 4-7 contains + log2 of line size; mask those to get cache size. */ +#define AT_L1I_CACHESHAPE 34 +#define AT_L1D_CACHESHAPE 35 +#define AT_L2_CACHESHAPE 36 +#define AT_L3_CACHESHAPE 37 + +/* Shapes of the caches, with more room to describe them. + *GEOMETRY are comprised of cache line size in bytes in the bottom 16 bits + and the cache associativity in the next 16 bits. */ +#define AT_L1I_CACHESIZE 40 +#define AT_L1I_CACHEGEOMETRY 41 +#define AT_L1D_CACHESIZE 42 +#define AT_L1D_CACHEGEOMETRY 43 +#define AT_L2_CACHESIZE 44 +#define AT_L2_CACHEGEOMETRY 45 +#define AT_L3_CACHESIZE 46 +#define AT_L3_CACHEGEOMETRY 47 + +#define AT_MINSIGSTKSZ 51 /* Stack needed for signal delivery + (AArch64). */ + +/* Note section contents. Each entry in the note section begins with + a header of a fixed form. */ + +typedef struct +{ + Elf32_Word n_namesz; /* Length of the note's name. */ + Elf32_Word n_descsz; /* Length of the note's descriptor. */ + Elf32_Word n_type; /* Type of the note. */ +} Elf32_Nhdr; + +typedef struct +{ + Elf64_Word n_namesz; /* Length of the note's name. */ + Elf64_Word n_descsz; /* Length of the note's descriptor. */ + Elf64_Word n_type; /* Type of the note. */ +} Elf64_Nhdr; + +/* Known names of notes. */ + +/* Solaris entries in the note section have this name. */ +#define ELF_NOTE_SOLARIS "SUNW Solaris" + +/* Note entries for GNU systems have this name. */ +#define ELF_NOTE_GNU "GNU" + + +/* Defined types of notes for Solaris. */ + +/* Value of descriptor (one word) is desired pagesize for the binary. */ +#define ELF_NOTE_PAGESIZE_HINT 1 + + +/* Defined note types for GNU systems. */ + +/* ABI information. The descriptor consists of words: + word 0: OS descriptor + word 1: major version of the ABI + word 2: minor version of the ABI + word 3: subminor version of the ABI +*/ +#define NT_GNU_ABI_TAG 1 +#define ELF_NOTE_ABI NT_GNU_ABI_TAG /* Old name. */ + +/* Known OSes. These values can appear in word 0 of an + NT_GNU_ABI_TAG note section entry. */ +#define ELF_NOTE_OS_LINUX 0 +#define ELF_NOTE_OS_GNU 1 +#define ELF_NOTE_OS_SOLARIS2 2 +#define ELF_NOTE_OS_FREEBSD 3 + +/* Synthetic hwcap information. The descriptor begins with two words: + word 0: number of entries + word 1: bitmask of enabled entries + Then follow variable-length entries, one byte followed by a + '\0'-terminated hwcap name string. The byte gives the bit + number to test if enabled, (1U << bit) & bitmask. */ +#define NT_GNU_HWCAP 2 + +/* Build ID bits as generated by ld --build-id. + The descriptor consists of any nonzero number of bytes. */ +#define NT_GNU_BUILD_ID 3 + +/* Version note generated by GNU gold containing a version string. */ +#define NT_GNU_GOLD_VERSION 4 + +/* Program property. */ +#define NT_GNU_PROPERTY_TYPE_0 5 + +/* Note section name of program property. */ +#define NOTE_GNU_PROPERTY_SECTION_NAME ".note.gnu.property" + +/* Values used in GNU .note.gnu.property notes (NT_GNU_PROPERTY_TYPE_0). */ + +/* Stack size. */ +#define GNU_PROPERTY_STACK_SIZE 1 +/* No copy relocation on protected data symbol. */ +#define GNU_PROPERTY_NO_COPY_ON_PROTECTED 2 + +/* Processor-specific semantics, lo */ +#define GNU_PROPERTY_LOPROC 0xc0000000 +/* Processor-specific semantics, hi */ +#define GNU_PROPERTY_HIPROC 0xdfffffff +/* Application-specific semantics, lo */ +#define GNU_PROPERTY_LOUSER 0xe0000000 +/* Application-specific semantics, hi */ +#define GNU_PROPERTY_HIUSER 0xffffffff + +/* The x86 instruction sets indicated by the corresponding bits are + used in program. Their support in the hardware is optional. */ +#define GNU_PROPERTY_X86_ISA_1_USED 0xc0000000 +/* The x86 instruction sets indicated by the corresponding bits are + used in program and they must be supported by the hardware. */ +#define GNU_PROPERTY_X86_ISA_1_NEEDED 0xc0000001 +/* X86 processor-specific features used in program. */ +#define GNU_PROPERTY_X86_FEATURE_1_AND 0xc0000002 + +#define GNU_PROPERTY_X86_ISA_1_486 (1U << 0) +#define GNU_PROPERTY_X86_ISA_1_586 (1U << 1) +#define GNU_PROPERTY_X86_ISA_1_686 (1U << 2) +#define GNU_PROPERTY_X86_ISA_1_SSE (1U << 3) +#define GNU_PROPERTY_X86_ISA_1_SSE2 (1U << 4) +#define GNU_PROPERTY_X86_ISA_1_SSE3 (1U << 5) +#define GNU_PROPERTY_X86_ISA_1_SSSE3 (1U << 6) +#define GNU_PROPERTY_X86_ISA_1_SSE4_1 (1U << 7) +#define GNU_PROPERTY_X86_ISA_1_SSE4_2 (1U << 8) +#define GNU_PROPERTY_X86_ISA_1_AVX (1U << 9) +#define GNU_PROPERTY_X86_ISA_1_AVX2 (1U << 10) +#define GNU_PROPERTY_X86_ISA_1_AVX512F (1U << 11) +#define GNU_PROPERTY_X86_ISA_1_AVX512CD (1U << 12) +#define GNU_PROPERTY_X86_ISA_1_AVX512ER (1U << 13) +#define GNU_PROPERTY_X86_ISA_1_AVX512PF (1U << 14) +#define GNU_PROPERTY_X86_ISA_1_AVX512VL (1U << 15) +#define GNU_PROPERTY_X86_ISA_1_AVX512DQ (1U << 16) +#define GNU_PROPERTY_X86_ISA_1_AVX512BW (1U << 17) + +/* This indicates that all executable sections are compatible with + IBT. */ +#define GNU_PROPERTY_X86_FEATURE_1_IBT (1U << 0) +/* This indicates that all executable sections are compatible with + SHSTK. */ +#define GNU_PROPERTY_X86_FEATURE_1_SHSTK (1U << 1) + +/* Move records. */ +typedef struct +{ + Elf32_Xword m_value; /* Symbol value. */ + Elf32_Word m_info; /* Size and index. */ + Elf32_Word m_poffset; /* Symbol offset. */ + Elf32_Half m_repeat; /* Repeat count. */ + Elf32_Half m_stride; /* Stride info. */ +} Elf32_Move; + +typedef struct +{ + Elf64_Xword m_value; /* Symbol value. */ + Elf64_Xword m_info; /* Size and index. */ + Elf64_Xword m_poffset; /* Symbol offset. */ + Elf64_Half m_repeat; /* Repeat count. */ + Elf64_Half m_stride; /* Stride info. */ +} Elf64_Move; + +/* Macro to construct move records. */ +#define ELF32_M_SYM(info) ((info) >> 8) +#define ELF32_M_SIZE(info) ((unsigned char) (info)) +#define ELF32_M_INFO(sym, size) (((sym) << 8) + (unsigned char) (size)) + +#define ELF64_M_SYM(info) ELF32_M_SYM (info) +#define ELF64_M_SIZE(info) ELF32_M_SIZE (info) +#define ELF64_M_INFO(sym, size) ELF32_M_INFO (sym, size) + + +/* Motorola 68k specific definitions. */ + +/* Values for Elf32_Ehdr.e_flags. */ +#define EF_CPU32 0x00810000 + +/* m68k relocs. */ + +#define R_68K_NONE 0 /* No reloc */ +#define R_68K_32 1 /* Direct 32 bit */ +#define R_68K_16 2 /* Direct 16 bit */ +#define R_68K_8 3 /* Direct 8 bit */ +#define R_68K_PC32 4 /* PC relative 32 bit */ +#define R_68K_PC16 5 /* PC relative 16 bit */ +#define R_68K_PC8 6 /* PC relative 8 bit */ +#define R_68K_GOT32 7 /* 32 bit PC relative GOT entry */ +#define R_68K_GOT16 8 /* 16 bit PC relative GOT entry */ +#define R_68K_GOT8 9 /* 8 bit PC relative GOT entry */ +#define R_68K_GOT32O 10 /* 32 bit GOT offset */ +#define R_68K_GOT16O 11 /* 16 bit GOT offset */ +#define R_68K_GOT8O 12 /* 8 bit GOT offset */ +#define R_68K_PLT32 13 /* 32 bit PC relative PLT address */ +#define R_68K_PLT16 14 /* 16 bit PC relative PLT address */ +#define R_68K_PLT8 15 /* 8 bit PC relative PLT address */ +#define R_68K_PLT32O 16 /* 32 bit PLT offset */ +#define R_68K_PLT16O 17 /* 16 bit PLT offset */ +#define R_68K_PLT8O 18 /* 8 bit PLT offset */ +#define R_68K_COPY 19 /* Copy symbol at runtime */ +#define R_68K_GLOB_DAT 20 /* Create GOT entry */ +#define R_68K_JMP_SLOT 21 /* Create PLT entry */ +#define R_68K_RELATIVE 22 /* Adjust by program base */ +#define R_68K_TLS_GD32 25 /* 32 bit GOT offset for GD */ +#define R_68K_TLS_GD16 26 /* 16 bit GOT offset for GD */ +#define R_68K_TLS_GD8 27 /* 8 bit GOT offset for GD */ +#define R_68K_TLS_LDM32 28 /* 32 bit GOT offset for LDM */ +#define R_68K_TLS_LDM16 29 /* 16 bit GOT offset for LDM */ +#define R_68K_TLS_LDM8 30 /* 8 bit GOT offset for LDM */ +#define R_68K_TLS_LDO32 31 /* 32 bit module-relative offset */ +#define R_68K_TLS_LDO16 32 /* 16 bit module-relative offset */ +#define R_68K_TLS_LDO8 33 /* 8 bit module-relative offset */ +#define R_68K_TLS_IE32 34 /* 32 bit GOT offset for IE */ +#define R_68K_TLS_IE16 35 /* 16 bit GOT offset for IE */ +#define R_68K_TLS_IE8 36 /* 8 bit GOT offset for IE */ +#define R_68K_TLS_LE32 37 /* 32 bit offset relative to + static TLS block */ +#define R_68K_TLS_LE16 38 /* 16 bit offset relative to + static TLS block */ +#define R_68K_TLS_LE8 39 /* 8 bit offset relative to + static TLS block */ +#define R_68K_TLS_DTPMOD32 40 /* 32 bit module number */ +#define R_68K_TLS_DTPREL32 41 /* 32 bit module-relative offset */ +#define R_68K_TLS_TPREL32 42 /* 32 bit TP-relative offset */ +/* Keep this the last entry. */ +#define R_68K_NUM 43 + +/* Intel 80386 specific definitions. */ + +/* i386 relocs. */ + +#define R_386_NONE 0 /* No reloc */ +#define R_386_32 1 /* Direct 32 bit */ +#define R_386_PC32 2 /* PC relative 32 bit */ +#define R_386_GOT32 3 /* 32 bit GOT entry */ +#define R_386_PLT32 4 /* 32 bit PLT address */ +#define R_386_COPY 5 /* Copy symbol at runtime */ +#define R_386_GLOB_DAT 6 /* Create GOT entry */ +#define R_386_JMP_SLOT 7 /* Create PLT entry */ +#define R_386_RELATIVE 8 /* Adjust by program base */ +#define R_386_GOTOFF 9 /* 32 bit offset to GOT */ +#define R_386_GOTPC 10 /* 32 bit PC relative offset to GOT */ +#define R_386_32PLT 11 +#define R_386_TLS_TPOFF 14 /* Offset in static TLS block */ +#define R_386_TLS_IE 15 /* Address of GOT entry for static TLS + block offset */ +#define R_386_TLS_GOTIE 16 /* GOT entry for static TLS block + offset */ +#define R_386_TLS_LE 17 /* Offset relative to static TLS + block */ +#define R_386_TLS_GD 18 /* Direct 32 bit for GNU version of + general dynamic thread local data */ +#define R_386_TLS_LDM 19 /* Direct 32 bit for GNU version of + local dynamic thread local data + in LE code */ +#define R_386_16 20 +#define R_386_PC16 21 +#define R_386_8 22 +#define R_386_PC8 23 +#define R_386_TLS_GD_32 24 /* Direct 32 bit for general dynamic + thread local data */ +#define R_386_TLS_GD_PUSH 25 /* Tag for pushl in GD TLS code */ +#define R_386_TLS_GD_CALL 26 /* Relocation for call to + __tls_get_addr() */ +#define R_386_TLS_GD_POP 27 /* Tag for popl in GD TLS code */ +#define R_386_TLS_LDM_32 28 /* Direct 32 bit for local dynamic + thread local data in LE code */ +#define R_386_TLS_LDM_PUSH 29 /* Tag for pushl in LDM TLS code */ +#define R_386_TLS_LDM_CALL 30 /* Relocation for call to + __tls_get_addr() in LDM code */ +#define R_386_TLS_LDM_POP 31 /* Tag for popl in LDM TLS code */ +#define R_386_TLS_LDO_32 32 /* Offset relative to TLS block */ +#define R_386_TLS_IE_32 33 /* GOT entry for negated static TLS + block offset */ +#define R_386_TLS_LE_32 34 /* Negated offset relative to static + TLS block */ +#define R_386_TLS_DTPMOD32 35 /* ID of module containing symbol */ +#define R_386_TLS_DTPOFF32 36 /* Offset in TLS block */ +#define R_386_TLS_TPOFF32 37 /* Negated offset in static TLS block */ +#define R_386_SIZE32 38 /* 32-bit symbol size */ +#define R_386_TLS_GOTDESC 39 /* GOT offset for TLS descriptor. */ +#define R_386_TLS_DESC_CALL 40 /* Marker of call through TLS + descriptor for + relaxation. */ +#define R_386_TLS_DESC 41 /* TLS descriptor containing + pointer to code and to + argument, returning the TLS + offset for the symbol. */ +#define R_386_IRELATIVE 42 /* Adjust indirectly by program base */ +#define R_386_GOT32X 43 /* Load from 32 bit GOT entry, + relaxable. */ +/* Keep this the last entry. */ +#define R_386_NUM 44 + +/* SUN SPARC specific definitions. */ + +/* Legal values for ST_TYPE subfield of st_info (symbol type). */ + +#define STT_SPARC_REGISTER 13 /* Global register reserved to app. */ + +/* Values for Elf64_Ehdr.e_flags. */ + +#define EF_SPARCV9_MM 3 +#define EF_SPARCV9_TSO 0 +#define EF_SPARCV9_PSO 1 +#define EF_SPARCV9_RMO 2 +#define EF_SPARC_LEDATA 0x800000 /* little endian data */ +#define EF_SPARC_EXT_MASK 0xFFFF00 +#define EF_SPARC_32PLUS 0x000100 /* generic V8+ features */ +#define EF_SPARC_SUN_US1 0x000200 /* Sun UltraSPARC1 extensions */ +#define EF_SPARC_HAL_R1 0x000400 /* HAL R1 extensions */ +#define EF_SPARC_SUN_US3 0x000800 /* Sun UltraSPARCIII extensions */ + +/* SPARC relocs. */ + +#define R_SPARC_NONE 0 /* No reloc */ +#define R_SPARC_8 1 /* Direct 8 bit */ +#define R_SPARC_16 2 /* Direct 16 bit */ +#define R_SPARC_32 3 /* Direct 32 bit */ +#define R_SPARC_DISP8 4 /* PC relative 8 bit */ +#define R_SPARC_DISP16 5 /* PC relative 16 bit */ +#define R_SPARC_DISP32 6 /* PC relative 32 bit */ +#define R_SPARC_WDISP30 7 /* PC relative 30 bit shifted */ +#define R_SPARC_WDISP22 8 /* PC relative 22 bit shifted */ +#define R_SPARC_HI22 9 /* High 22 bit */ +#define R_SPARC_22 10 /* Direct 22 bit */ +#define R_SPARC_13 11 /* Direct 13 bit */ +#define R_SPARC_LO10 12 /* Truncated 10 bit */ +#define R_SPARC_GOT10 13 /* Truncated 10 bit GOT entry */ +#define R_SPARC_GOT13 14 /* 13 bit GOT entry */ +#define R_SPARC_GOT22 15 /* 22 bit GOT entry shifted */ +#define R_SPARC_PC10 16 /* PC relative 10 bit truncated */ +#define R_SPARC_PC22 17 /* PC relative 22 bit shifted */ +#define R_SPARC_WPLT30 18 /* 30 bit PC relative PLT address */ +#define R_SPARC_COPY 19 /* Copy symbol at runtime */ +#define R_SPARC_GLOB_DAT 20 /* Create GOT entry */ +#define R_SPARC_JMP_SLOT 21 /* Create PLT entry */ +#define R_SPARC_RELATIVE 22 /* Adjust by program base */ +#define R_SPARC_UA32 23 /* Direct 32 bit unaligned */ + +/* Additional Sparc64 relocs. */ + +#define R_SPARC_PLT32 24 /* Direct 32 bit ref to PLT entry */ +#define R_SPARC_HIPLT22 25 /* High 22 bit PLT entry */ +#define R_SPARC_LOPLT10 26 /* Truncated 10 bit PLT entry */ +#define R_SPARC_PCPLT32 27 /* PC rel 32 bit ref to PLT entry */ +#define R_SPARC_PCPLT22 28 /* PC rel high 22 bit PLT entry */ +#define R_SPARC_PCPLT10 29 /* PC rel trunc 10 bit PLT entry */ +#define R_SPARC_10 30 /* Direct 10 bit */ +#define R_SPARC_11 31 /* Direct 11 bit */ +#define R_SPARC_64 32 /* Direct 64 bit */ +#define R_SPARC_OLO10 33 /* 10bit with secondary 13bit addend */ +#define R_SPARC_HH22 34 /* Top 22 bits of direct 64 bit */ +#define R_SPARC_HM10 35 /* High middle 10 bits of ... */ +#define R_SPARC_LM22 36 /* Low middle 22 bits of ... */ +#define R_SPARC_PC_HH22 37 /* Top 22 bits of pc rel 64 bit */ +#define R_SPARC_PC_HM10 38 /* High middle 10 bit of ... */ +#define R_SPARC_PC_LM22 39 /* Low miggle 22 bits of ... */ +#define R_SPARC_WDISP16 40 /* PC relative 16 bit shifted */ +#define R_SPARC_WDISP19 41 /* PC relative 19 bit shifted */ +#define R_SPARC_GLOB_JMP 42 /* was part of v9 ABI but was removed */ +#define R_SPARC_7 43 /* Direct 7 bit */ +#define R_SPARC_5 44 /* Direct 5 bit */ +#define R_SPARC_6 45 /* Direct 6 bit */ +#define R_SPARC_DISP64 46 /* PC relative 64 bit */ +#define R_SPARC_PLT64 47 /* Direct 64 bit ref to PLT entry */ +#define R_SPARC_HIX22 48 /* High 22 bit complemented */ +#define R_SPARC_LOX10 49 /* Truncated 11 bit complemented */ +#define R_SPARC_H44 50 /* Direct high 12 of 44 bit */ +#define R_SPARC_M44 51 /* Direct mid 22 of 44 bit */ +#define R_SPARC_L44 52 /* Direct low 10 of 44 bit */ +#define R_SPARC_REGISTER 53 /* Global register usage */ +#define R_SPARC_UA64 54 /* Direct 64 bit unaligned */ +#define R_SPARC_UA16 55 /* Direct 16 bit unaligned */ +#define R_SPARC_TLS_GD_HI22 56 +#define R_SPARC_TLS_GD_LO10 57 +#define R_SPARC_TLS_GD_ADD 58 +#define R_SPARC_TLS_GD_CALL 59 +#define R_SPARC_TLS_LDM_HI22 60 +#define R_SPARC_TLS_LDM_LO10 61 +#define R_SPARC_TLS_LDM_ADD 62 +#define R_SPARC_TLS_LDM_CALL 63 +#define R_SPARC_TLS_LDO_HIX22 64 +#define R_SPARC_TLS_LDO_LOX10 65 +#define R_SPARC_TLS_LDO_ADD 66 +#define R_SPARC_TLS_IE_HI22 67 +#define R_SPARC_TLS_IE_LO10 68 +#define R_SPARC_TLS_IE_LD 69 +#define R_SPARC_TLS_IE_LDX 70 +#define R_SPARC_TLS_IE_ADD 71 +#define R_SPARC_TLS_LE_HIX22 72 +#define R_SPARC_TLS_LE_LOX10 73 +#define R_SPARC_TLS_DTPMOD32 74 +#define R_SPARC_TLS_DTPMOD64 75 +#define R_SPARC_TLS_DTPOFF32 76 +#define R_SPARC_TLS_DTPOFF64 77 +#define R_SPARC_TLS_TPOFF32 78 +#define R_SPARC_TLS_TPOFF64 79 +#define R_SPARC_GOTDATA_HIX22 80 +#define R_SPARC_GOTDATA_LOX10 81 +#define R_SPARC_GOTDATA_OP_HIX22 82 +#define R_SPARC_GOTDATA_OP_LOX10 83 +#define R_SPARC_GOTDATA_OP 84 +#define R_SPARC_H34 85 +#define R_SPARC_SIZE32 86 +#define R_SPARC_SIZE64 87 +#define R_SPARC_WDISP10 88 +#define R_SPARC_JMP_IREL 248 +#define R_SPARC_IRELATIVE 249 +#define R_SPARC_GNU_VTINHERIT 250 +#define R_SPARC_GNU_VTENTRY 251 +#define R_SPARC_REV32 252 +/* Keep this the last entry. */ +#define R_SPARC_NUM 253 + +/* For Sparc64, legal values for d_tag of Elf64_Dyn. */ + +#define DT_SPARC_REGISTER 0x70000001 +#define DT_SPARC_NUM 2 + +/* MIPS R3000 specific definitions. */ + +/* Legal values for e_flags field of Elf32_Ehdr. */ + +#define EF_MIPS_NOREORDER 1 /* A .noreorder directive was used. */ +#define EF_MIPS_PIC 2 /* Contains PIC code. */ +#define EF_MIPS_CPIC 4 /* Uses PIC calling sequence. */ +#define EF_MIPS_XGOT 8 +#define EF_MIPS_64BIT_WHIRL 16 +#define EF_MIPS_ABI2 32 +#define EF_MIPS_ABI_ON32 64 +#define EF_MIPS_FP64 512 /* Uses FP64 (12 callee-saved). */ +#define EF_MIPS_NAN2008 1024 /* Uses IEEE 754-2008 NaN encoding. */ +#define EF_MIPS_ARCH 0xf0000000 /* MIPS architecture level. */ + +/* Legal values for MIPS architecture level. */ + +#define EF_MIPS_ARCH_1 0x00000000 /* -mips1 code. */ +#define EF_MIPS_ARCH_2 0x10000000 /* -mips2 code. */ +#define EF_MIPS_ARCH_3 0x20000000 /* -mips3 code. */ +#define EF_MIPS_ARCH_4 0x30000000 /* -mips4 code. */ +#define EF_MIPS_ARCH_5 0x40000000 /* -mips5 code. */ +#define EF_MIPS_ARCH_32 0x50000000 /* MIPS32 code. */ +#define EF_MIPS_ARCH_64 0x60000000 /* MIPS64 code. */ +#define EF_MIPS_ARCH_32R2 0x70000000 /* MIPS32r2 code. */ +#define EF_MIPS_ARCH_64R2 0x80000000 /* MIPS64r2 code. */ + +/* The following are unofficial names and should not be used. */ + +#define E_MIPS_ARCH_1 EF_MIPS_ARCH_1 +#define E_MIPS_ARCH_2 EF_MIPS_ARCH_2 +#define E_MIPS_ARCH_3 EF_MIPS_ARCH_3 +#define E_MIPS_ARCH_4 EF_MIPS_ARCH_4 +#define E_MIPS_ARCH_5 EF_MIPS_ARCH_5 +#define E_MIPS_ARCH_32 EF_MIPS_ARCH_32 +#define E_MIPS_ARCH_64 EF_MIPS_ARCH_64 + +/* Special section indices. */ + +#define SHN_MIPS_ACOMMON 0xff00 /* Allocated common symbols. */ +#define SHN_MIPS_TEXT 0xff01 /* Allocated test symbols. */ +#define SHN_MIPS_DATA 0xff02 /* Allocated data symbols. */ +#define SHN_MIPS_SCOMMON 0xff03 /* Small common symbols. */ +#define SHN_MIPS_SUNDEFINED 0xff04 /* Small undefined symbols. */ + +/* Legal values for sh_type field of Elf32_Shdr. */ + +#define SHT_MIPS_LIBLIST 0x70000000 /* Shared objects used in link. */ +#define SHT_MIPS_MSYM 0x70000001 +#define SHT_MIPS_CONFLICT 0x70000002 /* Conflicting symbols. */ +#define SHT_MIPS_GPTAB 0x70000003 /* Global data area sizes. */ +#define SHT_MIPS_UCODE 0x70000004 /* Reserved for SGI/MIPS compilers */ +#define SHT_MIPS_DEBUG 0x70000005 /* MIPS ECOFF debugging info. */ +#define SHT_MIPS_REGINFO 0x70000006 /* Register usage information. */ +#define SHT_MIPS_PACKAGE 0x70000007 +#define SHT_MIPS_PACKSYM 0x70000008 +#define SHT_MIPS_RELD 0x70000009 +#define SHT_MIPS_IFACE 0x7000000b +#define SHT_MIPS_CONTENT 0x7000000c +#define SHT_MIPS_OPTIONS 0x7000000d /* Miscellaneous options. */ +#define SHT_MIPS_SHDR 0x70000010 +#define SHT_MIPS_FDESC 0x70000011 +#define SHT_MIPS_EXTSYM 0x70000012 +#define SHT_MIPS_DENSE 0x70000013 +#define SHT_MIPS_PDESC 0x70000014 +#define SHT_MIPS_LOCSYM 0x70000015 +#define SHT_MIPS_AUXSYM 0x70000016 +#define SHT_MIPS_OPTSYM 0x70000017 +#define SHT_MIPS_LOCSTR 0x70000018 +#define SHT_MIPS_LINE 0x70000019 +#define SHT_MIPS_RFDESC 0x7000001a +#define SHT_MIPS_DELTASYM 0x7000001b +#define SHT_MIPS_DELTAINST 0x7000001c +#define SHT_MIPS_DELTACLASS 0x7000001d +#define SHT_MIPS_DWARF 0x7000001e /* DWARF debugging information. */ +#define SHT_MIPS_DELTADECL 0x7000001f +#define SHT_MIPS_SYMBOL_LIB 0x70000020 +#define SHT_MIPS_EVENTS 0x70000021 /* Event section. */ +#define SHT_MIPS_TRANSLATE 0x70000022 +#define SHT_MIPS_PIXIE 0x70000023 +#define SHT_MIPS_XLATE 0x70000024 +#define SHT_MIPS_XLATE_DEBUG 0x70000025 +#define SHT_MIPS_WHIRL 0x70000026 +#define SHT_MIPS_EH_REGION 0x70000027 +#define SHT_MIPS_XLATE_OLD 0x70000028 +#define SHT_MIPS_PDR_EXCEPTION 0x70000029 + +/* Legal values for sh_flags field of Elf32_Shdr. */ + +#define SHF_MIPS_GPREL 0x10000000 /* Must be in global data area. */ +#define SHF_MIPS_MERGE 0x20000000 +#define SHF_MIPS_ADDR 0x40000000 +#define SHF_MIPS_STRINGS 0x80000000 +#define SHF_MIPS_NOSTRIP 0x08000000 +#define SHF_MIPS_LOCAL 0x04000000 +#define SHF_MIPS_NAMES 0x02000000 +#define SHF_MIPS_NODUPE 0x01000000 + + +/* Symbol tables. */ + +/* MIPS specific values for `st_other'. */ +#define STO_MIPS_DEFAULT 0x0 +#define STO_MIPS_INTERNAL 0x1 +#define STO_MIPS_HIDDEN 0x2 +#define STO_MIPS_PROTECTED 0x3 +#define STO_MIPS_PLT 0x8 +#define STO_MIPS_SC_ALIGN_UNUSED 0xff + +/* MIPS specific values for `st_info'. */ +#define STB_MIPS_SPLIT_COMMON 13 + +/* Entries found in sections of type SHT_MIPS_GPTAB. */ + +typedef union +{ + struct + { + Elf32_Word gt_current_g_value; /* -G value used for compilation. */ + Elf32_Word gt_unused; /* Not used. */ + } gt_header; /* First entry in section. */ + struct + { + Elf32_Word gt_g_value; /* If this value were used for -G. */ + Elf32_Word gt_bytes; /* This many bytes would be used. */ + } gt_entry; /* Subsequent entries in section. */ +} Elf32_gptab; + +/* Entry found in sections of type SHT_MIPS_REGINFO. */ + +typedef struct +{ + Elf32_Word ri_gprmask; /* General registers used. */ + Elf32_Word ri_cprmask[4]; /* Coprocessor registers used. */ + Elf32_Sword ri_gp_value; /* $gp register value. */ +} Elf32_RegInfo; + +/* Entries found in sections of type SHT_MIPS_OPTIONS. */ + +typedef struct +{ + unsigned char kind; /* Determines interpretation of the + variable part of descriptor. */ + unsigned char size; /* Size of descriptor, including header. */ + Elf32_Section section; /* Section header index of section affected, + 0 for global options. */ + Elf32_Word info; /* Kind-specific information. */ +} Elf_Options; + +/* Values for `kind' field in Elf_Options. */ + +#define ODK_NULL 0 /* Undefined. */ +#define ODK_REGINFO 1 /* Register usage information. */ +#define ODK_EXCEPTIONS 2 /* Exception processing options. */ +#define ODK_PAD 3 /* Section padding options. */ +#define ODK_HWPATCH 4 /* Hardware workarounds performed */ +#define ODK_FILL 5 /* record the fill value used by the linker. */ +#define ODK_TAGS 6 /* reserve space for desktop tools to write. */ +#define ODK_HWAND 7 /* HW workarounds. 'AND' bits when merging. */ +#define ODK_HWOR 8 /* HW workarounds. 'OR' bits when merging. */ + +/* Values for `info' in Elf_Options for ODK_EXCEPTIONS entries. */ + +#define OEX_FPU_MIN 0x1f /* FPE's which MUST be enabled. */ +#define OEX_FPU_MAX 0x1f00 /* FPE's which MAY be enabled. */ +#define OEX_PAGE0 0x10000 /* page zero must be mapped. */ +#define OEX_SMM 0x20000 /* Force sequential memory mode? */ +#define OEX_FPDBUG 0x40000 /* Force floating point debug mode? */ +#define OEX_PRECISEFP OEX_FPDBUG +#define OEX_DISMISS 0x80000 /* Dismiss invalid address faults? */ + +#define OEX_FPU_INVAL 0x10 +#define OEX_FPU_DIV0 0x08 +#define OEX_FPU_OFLO 0x04 +#define OEX_FPU_UFLO 0x02 +#define OEX_FPU_INEX 0x01 + +/* Masks for `info' in Elf_Options for an ODK_HWPATCH entry. */ + +#define OHW_R4KEOP 0x1 /* R4000 end-of-page patch. */ +#define OHW_R8KPFETCH 0x2 /* may need R8000 prefetch patch. */ +#define OHW_R5KEOP 0x4 /* R5000 end-of-page patch. */ +#define OHW_R5KCVTL 0x8 /* R5000 cvt.[ds].l bug. clean=1. */ + +#define OPAD_PREFIX 0x1 +#define OPAD_POSTFIX 0x2 +#define OPAD_SYMBOL 0x4 + +/* Entry found in `.options' section. */ + +typedef struct +{ + Elf32_Word hwp_flags1; /* Extra flags. */ + Elf32_Word hwp_flags2; /* Extra flags. */ +} Elf_Options_Hw; + +/* Masks for `info' in ElfOptions for ODK_HWAND and ODK_HWOR entries. */ + +#define OHWA0_R4KEOP_CHECKED 0x00000001 +#define OHWA1_R4KEOP_CLEAN 0x00000002 + +/* MIPS relocs. */ + +#define R_MIPS_NONE 0 /* No reloc */ +#define R_MIPS_16 1 /* Direct 16 bit */ +#define R_MIPS_32 2 /* Direct 32 bit */ +#define R_MIPS_REL32 3 /* PC relative 32 bit */ +#define R_MIPS_26 4 /* Direct 26 bit shifted */ +#define R_MIPS_HI16 5 /* High 16 bit */ +#define R_MIPS_LO16 6 /* Low 16 bit */ +#define R_MIPS_GPREL16 7 /* GP relative 16 bit */ +#define R_MIPS_LITERAL 8 /* 16 bit literal entry */ +#define R_MIPS_GOT16 9 /* 16 bit GOT entry */ +#define R_MIPS_PC16 10 /* PC relative 16 bit */ +#define R_MIPS_CALL16 11 /* 16 bit GOT entry for function */ +#define R_MIPS_GPREL32 12 /* GP relative 32 bit */ + +#define R_MIPS_SHIFT5 16 +#define R_MIPS_SHIFT6 17 +#define R_MIPS_64 18 +#define R_MIPS_GOT_DISP 19 +#define R_MIPS_GOT_PAGE 20 +#define R_MIPS_GOT_OFST 21 +#define R_MIPS_GOT_HI16 22 +#define R_MIPS_GOT_LO16 23 +#define R_MIPS_SUB 24 +#define R_MIPS_INSERT_A 25 +#define R_MIPS_INSERT_B 26 +#define R_MIPS_DELETE 27 +#define R_MIPS_HIGHER 28 +#define R_MIPS_HIGHEST 29 +#define R_MIPS_CALL_HI16 30 +#define R_MIPS_CALL_LO16 31 +#define R_MIPS_SCN_DISP 32 +#define R_MIPS_REL16 33 +#define R_MIPS_ADD_IMMEDIATE 34 +#define R_MIPS_PJUMP 35 +#define R_MIPS_RELGOT 36 +#define R_MIPS_JALR 37 +#define R_MIPS_TLS_DTPMOD32 38 /* Module number 32 bit */ +#define R_MIPS_TLS_DTPREL32 39 /* Module-relative offset 32 bit */ +#define R_MIPS_TLS_DTPMOD64 40 /* Module number 64 bit */ +#define R_MIPS_TLS_DTPREL64 41 /* Module-relative offset 64 bit */ +#define R_MIPS_TLS_GD 42 /* 16 bit GOT offset for GD */ +#define R_MIPS_TLS_LDM 43 /* 16 bit GOT offset for LDM */ +#define R_MIPS_TLS_DTPREL_HI16 44 /* Module-relative offset, high 16 bits */ +#define R_MIPS_TLS_DTPREL_LO16 45 /* Module-relative offset, low 16 bits */ +#define R_MIPS_TLS_GOTTPREL 46 /* 16 bit GOT offset for IE */ +#define R_MIPS_TLS_TPREL32 47 /* TP-relative offset, 32 bit */ +#define R_MIPS_TLS_TPREL64 48 /* TP-relative offset, 64 bit */ +#define R_MIPS_TLS_TPREL_HI16 49 /* TP-relative offset, high 16 bits */ +#define R_MIPS_TLS_TPREL_LO16 50 /* TP-relative offset, low 16 bits */ +#define R_MIPS_GLOB_DAT 51 +#define R_MIPS_COPY 126 +#define R_MIPS_JUMP_SLOT 127 +/* Keep this the last entry. */ +#define R_MIPS_NUM 128 + +/* Legal values for p_type field of Elf32_Phdr. */ + +#define PT_MIPS_REGINFO 0x70000000 /* Register usage information. */ +#define PT_MIPS_RTPROC 0x70000001 /* Runtime procedure table. */ +#define PT_MIPS_OPTIONS 0x70000002 +#define PT_MIPS_ABIFLAGS 0x70000003 /* FP mode requirement. */ + +/* Special program header types. */ + +#define PF_MIPS_LOCAL 0x10000000 + +/* Legal values for d_tag field of Elf32_Dyn. */ + +#define DT_MIPS_RLD_VERSION 0x70000001 /* Runtime linker interface version */ +#define DT_MIPS_TIME_STAMP 0x70000002 /* Timestamp */ +#define DT_MIPS_ICHECKSUM 0x70000003 /* Checksum */ +#define DT_MIPS_IVERSION 0x70000004 /* Version string (string tbl index) */ +#define DT_MIPS_FLAGS 0x70000005 /* Flags */ +#define DT_MIPS_BASE_ADDRESS 0x70000006 /* Base address */ +#define DT_MIPS_MSYM 0x70000007 +#define DT_MIPS_CONFLICT 0x70000008 /* Address of CONFLICT section */ +#define DT_MIPS_LIBLIST 0x70000009 /* Address of LIBLIST section */ +#define DT_MIPS_LOCAL_GOTNO 0x7000000a /* Number of local GOT entries */ +#define DT_MIPS_CONFLICTNO 0x7000000b /* Number of CONFLICT entries */ +#define DT_MIPS_LIBLISTNO 0x70000010 /* Number of LIBLIST entries */ +#define DT_MIPS_SYMTABNO 0x70000011 /* Number of DYNSYM entries */ +#define DT_MIPS_UNREFEXTNO 0x70000012 /* First external DYNSYM */ +#define DT_MIPS_GOTSYM 0x70000013 /* First GOT entry in DYNSYM */ +#define DT_MIPS_HIPAGENO 0x70000014 /* Number of GOT page table entries */ +#define DT_MIPS_RLD_MAP 0x70000016 /* Address of run time loader map. */ +#define DT_MIPS_DELTA_CLASS 0x70000017 /* Delta C++ class definition. */ +#define DT_MIPS_DELTA_CLASS_NO 0x70000018 /* Number of entries in + DT_MIPS_DELTA_CLASS. */ +#define DT_MIPS_DELTA_INSTANCE 0x70000019 /* Delta C++ class instances. */ +#define DT_MIPS_DELTA_INSTANCE_NO 0x7000001a /* Number of entries in + DT_MIPS_DELTA_INSTANCE. */ +#define DT_MIPS_DELTA_RELOC 0x7000001b /* Delta relocations. */ +#define DT_MIPS_DELTA_RELOC_NO 0x7000001c /* Number of entries in + DT_MIPS_DELTA_RELOC. */ +#define DT_MIPS_DELTA_SYM 0x7000001d /* Delta symbols that Delta + relocations refer to. */ +#define DT_MIPS_DELTA_SYM_NO 0x7000001e /* Number of entries in + DT_MIPS_DELTA_SYM. */ +#define DT_MIPS_DELTA_CLASSSYM 0x70000020 /* Delta symbols that hold the + class declaration. */ +#define DT_MIPS_DELTA_CLASSSYM_NO 0x70000021 /* Number of entries in + DT_MIPS_DELTA_CLASSSYM. */ +#define DT_MIPS_CXX_FLAGS 0x70000022 /* Flags indicating for C++ flavor. */ +#define DT_MIPS_PIXIE_INIT 0x70000023 +#define DT_MIPS_SYMBOL_LIB 0x70000024 +#define DT_MIPS_LOCALPAGE_GOTIDX 0x70000025 +#define DT_MIPS_LOCAL_GOTIDX 0x70000026 +#define DT_MIPS_HIDDEN_GOTIDX 0x70000027 +#define DT_MIPS_PROTECTED_GOTIDX 0x70000028 +#define DT_MIPS_OPTIONS 0x70000029 /* Address of .options. */ +#define DT_MIPS_INTERFACE 0x7000002a /* Address of .interface. */ +#define DT_MIPS_DYNSTR_ALIGN 0x7000002b +#define DT_MIPS_INTERFACE_SIZE 0x7000002c /* Size of the .interface section. */ +#define DT_MIPS_RLD_TEXT_RESOLVE_ADDR 0x7000002d /* Address of rld_text_rsolve + function stored in GOT. */ +#define DT_MIPS_PERF_SUFFIX 0x7000002e /* Default suffix of dso to be added + by rld on dlopen() calls. */ +#define DT_MIPS_COMPACT_SIZE 0x7000002f /* (O32)Size of compact rel section. */ +#define DT_MIPS_GP_VALUE 0x70000030 /* GP value for aux GOTs. */ +#define DT_MIPS_AUX_DYNAMIC 0x70000031 /* Address of aux .dynamic. */ +/* The address of .got.plt in an executable using the new non-PIC ABI. */ +#define DT_MIPS_PLTGOT 0x70000032 +/* The base of the PLT in an executable using the new non-PIC ABI if that + PLT is writable. For a non-writable PLT, this is omitted or has a zero + value. */ +#define DT_MIPS_RWPLT 0x70000034 +/* An alternative description of the classic MIPS RLD_MAP that is usable + in a PIE as it stores a relative offset from the address of the tag + rather than an absolute address. */ +#define DT_MIPS_RLD_MAP_REL 0x70000035 +#define DT_MIPS_NUM 0x36 + +/* Legal values for DT_MIPS_FLAGS Elf32_Dyn entry. */ + +#define RHF_NONE 0 /* No flags */ +#define RHF_QUICKSTART (1 << 0) /* Use quickstart */ +#define RHF_NOTPOT (1 << 1) /* Hash size not power of 2 */ +#define RHF_NO_LIBRARY_REPLACEMENT (1 << 2) /* Ignore LD_LIBRARY_PATH */ +#define RHF_NO_MOVE (1 << 3) +#define RHF_SGI_ONLY (1 << 4) +#define RHF_GUARANTEE_INIT (1 << 5) +#define RHF_DELTA_C_PLUS_PLUS (1 << 6) +#define RHF_GUARANTEE_START_INIT (1 << 7) +#define RHF_PIXIE (1 << 8) +#define RHF_DEFAULT_DELAY_LOAD (1 << 9) +#define RHF_REQUICKSTART (1 << 10) +#define RHF_REQUICKSTARTED (1 << 11) +#define RHF_CORD (1 << 12) +#define RHF_NO_UNRES_UNDEF (1 << 13) +#define RHF_RLD_ORDER_SAFE (1 << 14) + +/* Entries found in sections of type SHT_MIPS_LIBLIST. */ + +typedef struct +{ + Elf32_Word l_name; /* Name (string table index) */ + Elf32_Word l_time_stamp; /* Timestamp */ + Elf32_Word l_checksum; /* Checksum */ + Elf32_Word l_version; /* Interface version */ + Elf32_Word l_flags; /* Flags */ +} Elf32_Lib; + +typedef struct +{ + Elf64_Word l_name; /* Name (string table index) */ + Elf64_Word l_time_stamp; /* Timestamp */ + Elf64_Word l_checksum; /* Checksum */ + Elf64_Word l_version; /* Interface version */ + Elf64_Word l_flags; /* Flags */ +} Elf64_Lib; + + +/* Legal values for l_flags. */ + +#define LL_NONE 0 +#define LL_EXACT_MATCH (1 << 0) /* Require exact match */ +#define LL_IGNORE_INT_VER (1 << 1) /* Ignore interface version */ +#define LL_REQUIRE_MINOR (1 << 2) +#define LL_EXPORTS (1 << 3) +#define LL_DELAY_LOAD (1 << 4) +#define LL_DELTA (1 << 5) + +/* Entries found in sections of type SHT_MIPS_CONFLICT. */ + +typedef Elf32_Addr Elf32_Conflict; + +typedef struct +{ + /* Version of flags structure. */ + Elf32_Half version; + /* The level of the ISA: 1-5, 32, 64. */ + unsigned char isa_level; + /* The revision of ISA: 0 for MIPS V and below, 1-n otherwise. */ + unsigned char isa_rev; + /* The size of general purpose registers. */ + unsigned char gpr_size; + /* The size of co-processor 1 registers. */ + unsigned char cpr1_size; + /* The size of co-processor 2 registers. */ + unsigned char cpr2_size; + /* The floating-point ABI. */ + unsigned char fp_abi; + /* Processor-specific extension. */ + Elf32_Word isa_ext; + /* Mask of ASEs used. */ + Elf32_Word ases; + /* Mask of general flags. */ + Elf32_Word flags1; + Elf32_Word flags2; +} Elf_MIPS_ABIFlags_v0; + +/* Values for the register size bytes of an abi flags structure. */ + +#define MIPS_AFL_REG_NONE 0x00 /* No registers. */ +#define MIPS_AFL_REG_32 0x01 /* 32-bit registers. */ +#define MIPS_AFL_REG_64 0x02 /* 64-bit registers. */ +#define MIPS_AFL_REG_128 0x03 /* 128-bit registers. */ + +/* Masks for the ases word of an ABI flags structure. */ + +#define MIPS_AFL_ASE_DSP 0x00000001 /* DSP ASE. */ +#define MIPS_AFL_ASE_DSPR2 0x00000002 /* DSP R2 ASE. */ +#define MIPS_AFL_ASE_EVA 0x00000004 /* Enhanced VA Scheme. */ +#define MIPS_AFL_ASE_MCU 0x00000008 /* MCU (MicroController) ASE. */ +#define MIPS_AFL_ASE_MDMX 0x00000010 /* MDMX ASE. */ +#define MIPS_AFL_ASE_MIPS3D 0x00000020 /* MIPS-3D ASE. */ +#define MIPS_AFL_ASE_MT 0x00000040 /* MT ASE. */ +#define MIPS_AFL_ASE_SMARTMIPS 0x00000080 /* SmartMIPS ASE. */ +#define MIPS_AFL_ASE_VIRT 0x00000100 /* VZ ASE. */ +#define MIPS_AFL_ASE_MSA 0x00000200 /* MSA ASE. */ +#define MIPS_AFL_ASE_MIPS16 0x00000400 /* MIPS16 ASE. */ +#define MIPS_AFL_ASE_MICROMIPS 0x00000800 /* MICROMIPS ASE. */ +#define MIPS_AFL_ASE_XPA 0x00001000 /* XPA ASE. */ +#define MIPS_AFL_ASE_MASK 0x00001fff /* All ASEs. */ + +/* Values for the isa_ext word of an ABI flags structure. */ + +#define MIPS_AFL_EXT_XLR 1 /* RMI Xlr instruction. */ +#define MIPS_AFL_EXT_OCTEON2 2 /* Cavium Networks Octeon2. */ +#define MIPS_AFL_EXT_OCTEONP 3 /* Cavium Networks OcteonP. */ +#define MIPS_AFL_EXT_LOONGSON_3A 4 /* Loongson 3A. */ +#define MIPS_AFL_EXT_OCTEON 5 /* Cavium Networks Octeon. */ +#define MIPS_AFL_EXT_5900 6 /* MIPS R5900 instruction. */ +#define MIPS_AFL_EXT_4650 7 /* MIPS R4650 instruction. */ +#define MIPS_AFL_EXT_4010 8 /* LSI R4010 instruction. */ +#define MIPS_AFL_EXT_4100 9 /* NEC VR4100 instruction. */ +#define MIPS_AFL_EXT_3900 10 /* Toshiba R3900 instruction. */ +#define MIPS_AFL_EXT_10000 11 /* MIPS R10000 instruction. */ +#define MIPS_AFL_EXT_SB1 12 /* Broadcom SB-1 instruction. */ +#define MIPS_AFL_EXT_4111 13 /* NEC VR4111/VR4181 instruction. */ +#define MIPS_AFL_EXT_4120 14 /* NEC VR4120 instruction. */ +#define MIPS_AFL_EXT_5400 15 /* NEC VR5400 instruction. */ +#define MIPS_AFL_EXT_5500 16 /* NEC VR5500 instruction. */ +#define MIPS_AFL_EXT_LOONGSON_2E 17 /* ST Microelectronics Loongson 2E. */ +#define MIPS_AFL_EXT_LOONGSON_2F 18 /* ST Microelectronics Loongson 2F. */ + +/* Masks for the flags1 word of an ABI flags structure. */ +#define MIPS_AFL_FLAGS1_ODDSPREG 1 /* Uses odd single-precision registers. */ + +/* Object attribute values. */ +enum +{ + /* Not tagged or not using any ABIs affected by the differences. */ + Val_GNU_MIPS_ABI_FP_ANY = 0, + /* Using hard-float -mdouble-float. */ + Val_GNU_MIPS_ABI_FP_DOUBLE = 1, + /* Using hard-float -msingle-float. */ + Val_GNU_MIPS_ABI_FP_SINGLE = 2, + /* Using soft-float. */ + Val_GNU_MIPS_ABI_FP_SOFT = 3, + /* Using -mips32r2 -mfp64. */ + Val_GNU_MIPS_ABI_FP_OLD_64 = 4, + /* Using -mfpxx. */ + Val_GNU_MIPS_ABI_FP_XX = 5, + /* Using -mips32r2 -mfp64. */ + Val_GNU_MIPS_ABI_FP_64 = 6, + /* Using -mips32r2 -mfp64 -mno-odd-spreg. */ + Val_GNU_MIPS_ABI_FP_64A = 7, + /* Maximum allocated FP ABI value. */ + Val_GNU_MIPS_ABI_FP_MAX = 7 +}; + +/* HPPA specific definitions. */ + +/* Legal values for e_flags field of Elf32_Ehdr. */ + +#define EF_PARISC_TRAPNIL 0x00010000 /* Trap nil pointer dereference. */ +#define EF_PARISC_EXT 0x00020000 /* Program uses arch. extensions. */ +#define EF_PARISC_LSB 0x00040000 /* Program expects little endian. */ +#define EF_PARISC_WIDE 0x00080000 /* Program expects wide mode. */ +#define EF_PARISC_NO_KABP 0x00100000 /* No kernel assisted branch + prediction. */ +#define EF_PARISC_LAZYSWAP 0x00400000 /* Allow lazy swapping. */ +#define EF_PARISC_ARCH 0x0000ffff /* Architecture version. */ + +/* Defined values for `e_flags & EF_PARISC_ARCH' are: */ + +#define EFA_PARISC_1_0 0x020b /* PA-RISC 1.0 big-endian. */ +#define EFA_PARISC_1_1 0x0210 /* PA-RISC 1.1 big-endian. */ +#define EFA_PARISC_2_0 0x0214 /* PA-RISC 2.0 big-endian. */ + +/* Additional section indeces. */ + +#define SHN_PARISC_ANSI_COMMON 0xff00 /* Section for tenatively declared + symbols in ANSI C. */ +#define SHN_PARISC_HUGE_COMMON 0xff01 /* Common blocks in huge model. */ + +/* Legal values for sh_type field of Elf32_Shdr. */ + +#define SHT_PARISC_EXT 0x70000000 /* Contains product specific ext. */ +#define SHT_PARISC_UNWIND 0x70000001 /* Unwind information. */ +#define SHT_PARISC_DOC 0x70000002 /* Debug info for optimized code. */ + +/* Legal values for sh_flags field of Elf32_Shdr. */ + +#define SHF_PARISC_SHORT 0x20000000 /* Section with short addressing. */ +#define SHF_PARISC_HUGE 0x40000000 /* Section far from gp. */ +#define SHF_PARISC_SBP 0x80000000 /* Static branch prediction code. */ + +/* Legal values for ST_TYPE subfield of st_info (symbol type). */ + +#define STT_PARISC_MILLICODE 13 /* Millicode function entry point. */ + +#define STT_HP_OPAQUE (STT_LOOS + 0x1) +#define STT_HP_STUB (STT_LOOS + 0x2) + +/* HPPA relocs. */ + +#define R_PARISC_NONE 0 /* No reloc. */ +#define R_PARISC_DIR32 1 /* Direct 32-bit reference. */ +#define R_PARISC_DIR21L 2 /* Left 21 bits of eff. address. */ +#define R_PARISC_DIR17R 3 /* Right 17 bits of eff. address. */ +#define R_PARISC_DIR17F 4 /* 17 bits of eff. address. */ +#define R_PARISC_DIR14R 6 /* Right 14 bits of eff. address. */ +#define R_PARISC_PCREL32 9 /* 32-bit rel. address. */ +#define R_PARISC_PCREL21L 10 /* Left 21 bits of rel. address. */ +#define R_PARISC_PCREL17R 11 /* Right 17 bits of rel. address. */ +#define R_PARISC_PCREL17F 12 /* 17 bits of rel. address. */ +#define R_PARISC_PCREL14R 14 /* Right 14 bits of rel. address. */ +#define R_PARISC_DPREL21L 18 /* Left 21 bits of rel. address. */ +#define R_PARISC_DPREL14R 22 /* Right 14 bits of rel. address. */ +#define R_PARISC_GPREL21L 26 /* GP-relative, left 21 bits. */ +#define R_PARISC_GPREL14R 30 /* GP-relative, right 14 bits. */ +#define R_PARISC_LTOFF21L 34 /* LT-relative, left 21 bits. */ +#define R_PARISC_LTOFF14R 38 /* LT-relative, right 14 bits. */ +#define R_PARISC_SECREL32 41 /* 32 bits section rel. address. */ +#define R_PARISC_SEGBASE 48 /* No relocation, set segment base. */ +#define R_PARISC_SEGREL32 49 /* 32 bits segment rel. address. */ +#define R_PARISC_PLTOFF21L 50 /* PLT rel. address, left 21 bits. */ +#define R_PARISC_PLTOFF14R 54 /* PLT rel. address, right 14 bits. */ +#define R_PARISC_LTOFF_FPTR32 57 /* 32 bits LT-rel. function pointer. */ +#define R_PARISC_LTOFF_FPTR21L 58 /* LT-rel. fct ptr, left 21 bits. */ +#define R_PARISC_LTOFF_FPTR14R 62 /* LT-rel. fct ptr, right 14 bits. */ +#define R_PARISC_FPTR64 64 /* 64 bits function address. */ +#define R_PARISC_PLABEL32 65 /* 32 bits function address. */ +#define R_PARISC_PLABEL21L 66 /* Left 21 bits of fdesc address. */ +#define R_PARISC_PLABEL14R 70 /* Right 14 bits of fdesc address. */ +#define R_PARISC_PCREL64 72 /* 64 bits PC-rel. address. */ +#define R_PARISC_PCREL22F 74 /* 22 bits PC-rel. address. */ +#define R_PARISC_PCREL14WR 75 /* PC-rel. address, right 14 bits. */ +#define R_PARISC_PCREL14DR 76 /* PC rel. address, right 14 bits. */ +#define R_PARISC_PCREL16F 77 /* 16 bits PC-rel. address. */ +#define R_PARISC_PCREL16WF 78 /* 16 bits PC-rel. address. */ +#define R_PARISC_PCREL16DF 79 /* 16 bits PC-rel. address. */ +#define R_PARISC_DIR64 80 /* 64 bits of eff. address. */ +#define R_PARISC_DIR14WR 83 /* 14 bits of eff. address. */ +#define R_PARISC_DIR14DR 84 /* 14 bits of eff. address. */ +#define R_PARISC_DIR16F 85 /* 16 bits of eff. address. */ +#define R_PARISC_DIR16WF 86 /* 16 bits of eff. address. */ +#define R_PARISC_DIR16DF 87 /* 16 bits of eff. address. */ +#define R_PARISC_GPREL64 88 /* 64 bits of GP-rel. address. */ +#define R_PARISC_GPREL14WR 91 /* GP-rel. address, right 14 bits. */ +#define R_PARISC_GPREL14DR 92 /* GP-rel. address, right 14 bits. */ +#define R_PARISC_GPREL16F 93 /* 16 bits GP-rel. address. */ +#define R_PARISC_GPREL16WF 94 /* 16 bits GP-rel. address. */ +#define R_PARISC_GPREL16DF 95 /* 16 bits GP-rel. address. */ +#define R_PARISC_LTOFF64 96 /* 64 bits LT-rel. address. */ +#define R_PARISC_LTOFF14WR 99 /* LT-rel. address, right 14 bits. */ +#define R_PARISC_LTOFF14DR 100 /* LT-rel. address, right 14 bits. */ +#define R_PARISC_LTOFF16F 101 /* 16 bits LT-rel. address. */ +#define R_PARISC_LTOFF16WF 102 /* 16 bits LT-rel. address. */ +#define R_PARISC_LTOFF16DF 103 /* 16 bits LT-rel. address. */ +#define R_PARISC_SECREL64 104 /* 64 bits section rel. address. */ +#define R_PARISC_SEGREL64 112 /* 64 bits segment rel. address. */ +#define R_PARISC_PLTOFF14WR 115 /* PLT-rel. address, right 14 bits. */ +#define R_PARISC_PLTOFF14DR 116 /* PLT-rel. address, right 14 bits. */ +#define R_PARISC_PLTOFF16F 117 /* 16 bits LT-rel. address. */ +#define R_PARISC_PLTOFF16WF 118 /* 16 bits PLT-rel. address. */ +#define R_PARISC_PLTOFF16DF 119 /* 16 bits PLT-rel. address. */ +#define R_PARISC_LTOFF_FPTR64 120 /* 64 bits LT-rel. function ptr. */ +#define R_PARISC_LTOFF_FPTR14WR 123 /* LT-rel. fct. ptr., right 14 bits. */ +#define R_PARISC_LTOFF_FPTR14DR 124 /* LT-rel. fct. ptr., right 14 bits. */ +#define R_PARISC_LTOFF_FPTR16F 125 /* 16 bits LT-rel. function ptr. */ +#define R_PARISC_LTOFF_FPTR16WF 126 /* 16 bits LT-rel. function ptr. */ +#define R_PARISC_LTOFF_FPTR16DF 127 /* 16 bits LT-rel. function ptr. */ +#define R_PARISC_LORESERVE 128 +#define R_PARISC_COPY 128 /* Copy relocation. */ +#define R_PARISC_IPLT 129 /* Dynamic reloc, imported PLT */ +#define R_PARISC_EPLT 130 /* Dynamic reloc, exported PLT */ +#define R_PARISC_TPREL32 153 /* 32 bits TP-rel. address. */ +#define R_PARISC_TPREL21L 154 /* TP-rel. address, left 21 bits. */ +#define R_PARISC_TPREL14R 158 /* TP-rel. address, right 14 bits. */ +#define R_PARISC_LTOFF_TP21L 162 /* LT-TP-rel. address, left 21 bits. */ +#define R_PARISC_LTOFF_TP14R 166 /* LT-TP-rel. address, right 14 bits.*/ +#define R_PARISC_LTOFF_TP14F 167 /* 14 bits LT-TP-rel. address. */ +#define R_PARISC_TPREL64 216 /* 64 bits TP-rel. address. */ +#define R_PARISC_TPREL14WR 219 /* TP-rel. address, right 14 bits. */ +#define R_PARISC_TPREL14DR 220 /* TP-rel. address, right 14 bits. */ +#define R_PARISC_TPREL16F 221 /* 16 bits TP-rel. address. */ +#define R_PARISC_TPREL16WF 222 /* 16 bits TP-rel. address. */ +#define R_PARISC_TPREL16DF 223 /* 16 bits TP-rel. address. */ +#define R_PARISC_LTOFF_TP64 224 /* 64 bits LT-TP-rel. address. */ +#define R_PARISC_LTOFF_TP14WR 227 /* LT-TP-rel. address, right 14 bits.*/ +#define R_PARISC_LTOFF_TP14DR 228 /* LT-TP-rel. address, right 14 bits.*/ +#define R_PARISC_LTOFF_TP16F 229 /* 16 bits LT-TP-rel. address. */ +#define R_PARISC_LTOFF_TP16WF 230 /* 16 bits LT-TP-rel. address. */ +#define R_PARISC_LTOFF_TP16DF 231 /* 16 bits LT-TP-rel. address. */ +#define R_PARISC_GNU_VTENTRY 232 +#define R_PARISC_GNU_VTINHERIT 233 +#define R_PARISC_TLS_GD21L 234 /* GD 21-bit left. */ +#define R_PARISC_TLS_GD14R 235 /* GD 14-bit right. */ +#define R_PARISC_TLS_GDCALL 236 /* GD call to __t_g_a. */ +#define R_PARISC_TLS_LDM21L 237 /* LD module 21-bit left. */ +#define R_PARISC_TLS_LDM14R 238 /* LD module 14-bit right. */ +#define R_PARISC_TLS_LDMCALL 239 /* LD module call to __t_g_a. */ +#define R_PARISC_TLS_LDO21L 240 /* LD offset 21-bit left. */ +#define R_PARISC_TLS_LDO14R 241 /* LD offset 14-bit right. */ +#define R_PARISC_TLS_DTPMOD32 242 /* DTP module 32-bit. */ +#define R_PARISC_TLS_DTPMOD64 243 /* DTP module 64-bit. */ +#define R_PARISC_TLS_DTPOFF32 244 /* DTP offset 32-bit. */ +#define R_PARISC_TLS_DTPOFF64 245 /* DTP offset 32-bit. */ +#define R_PARISC_TLS_LE21L R_PARISC_TPREL21L +#define R_PARISC_TLS_LE14R R_PARISC_TPREL14R +#define R_PARISC_TLS_IE21L R_PARISC_LTOFF_TP21L +#define R_PARISC_TLS_IE14R R_PARISC_LTOFF_TP14R +#define R_PARISC_TLS_TPREL32 R_PARISC_TPREL32 +#define R_PARISC_TLS_TPREL64 R_PARISC_TPREL64 +#define R_PARISC_HIRESERVE 255 + +/* Legal values for p_type field of Elf32_Phdr/Elf64_Phdr. */ + +#define PT_HP_TLS (PT_LOOS + 0x0) +#define PT_HP_CORE_NONE (PT_LOOS + 0x1) +#define PT_HP_CORE_VERSION (PT_LOOS + 0x2) +#define PT_HP_CORE_KERNEL (PT_LOOS + 0x3) +#define PT_HP_CORE_COMM (PT_LOOS + 0x4) +#define PT_HP_CORE_PROC (PT_LOOS + 0x5) +#define PT_HP_CORE_LOADABLE (PT_LOOS + 0x6) +#define PT_HP_CORE_STACK (PT_LOOS + 0x7) +#define PT_HP_CORE_SHM (PT_LOOS + 0x8) +#define PT_HP_CORE_MMF (PT_LOOS + 0x9) +#define PT_HP_PARALLEL (PT_LOOS + 0x10) +#define PT_HP_FASTBIND (PT_LOOS + 0x11) +#define PT_HP_OPT_ANNOT (PT_LOOS + 0x12) +#define PT_HP_HSL_ANNOT (PT_LOOS + 0x13) +#define PT_HP_STACK (PT_LOOS + 0x14) + +#define PT_PARISC_ARCHEXT 0x70000000 +#define PT_PARISC_UNWIND 0x70000001 + +/* Legal values for p_flags field of Elf32_Phdr/Elf64_Phdr. */ + +#define PF_PARISC_SBP 0x08000000 + +#define PF_HP_PAGE_SIZE 0x00100000 +#define PF_HP_FAR_SHARED 0x00200000 +#define PF_HP_NEAR_SHARED 0x00400000 +#define PF_HP_CODE 0x01000000 +#define PF_HP_MODIFY 0x02000000 +#define PF_HP_LAZYSWAP 0x04000000 +#define PF_HP_SBP 0x08000000 + + +/* Alpha specific definitions. */ + +/* Legal values for e_flags field of Elf64_Ehdr. */ + +#define EF_ALPHA_32BIT 1 /* All addresses must be < 2GB. */ +#define EF_ALPHA_CANRELAX 2 /* Relocations for relaxing exist. */ + +/* Legal values for sh_type field of Elf64_Shdr. */ + +/* These two are primerily concerned with ECOFF debugging info. */ +#define SHT_ALPHA_DEBUG 0x70000001 +#define SHT_ALPHA_REGINFO 0x70000002 + +/* Legal values for sh_flags field of Elf64_Shdr. */ + +#define SHF_ALPHA_GPREL 0x10000000 + +/* Legal values for st_other field of Elf64_Sym. */ +#define STO_ALPHA_NOPV 0x80 /* No PV required. */ +#define STO_ALPHA_STD_GPLOAD 0x88 /* PV only used for initial ldgp. */ + +/* Alpha relocs. */ + +#define R_ALPHA_NONE 0 /* No reloc */ +#define R_ALPHA_REFLONG 1 /* Direct 32 bit */ +#define R_ALPHA_REFQUAD 2 /* Direct 64 bit */ +#define R_ALPHA_GPREL32 3 /* GP relative 32 bit */ +#define R_ALPHA_LITERAL 4 /* GP relative 16 bit w/optimization */ +#define R_ALPHA_LITUSE 5 /* Optimization hint for LITERAL */ +#define R_ALPHA_GPDISP 6 /* Add displacement to GP */ +#define R_ALPHA_BRADDR 7 /* PC+4 relative 23 bit shifted */ +#define R_ALPHA_HINT 8 /* PC+4 relative 16 bit shifted */ +#define R_ALPHA_SREL16 9 /* PC relative 16 bit */ +#define R_ALPHA_SREL32 10 /* PC relative 32 bit */ +#define R_ALPHA_SREL64 11 /* PC relative 64 bit */ +#define R_ALPHA_GPRELHIGH 17 /* GP relative 32 bit, high 16 bits */ +#define R_ALPHA_GPRELLOW 18 /* GP relative 32 bit, low 16 bits */ +#define R_ALPHA_GPREL16 19 /* GP relative 16 bit */ +#define R_ALPHA_COPY 24 /* Copy symbol at runtime */ +#define R_ALPHA_GLOB_DAT 25 /* Create GOT entry */ +#define R_ALPHA_JMP_SLOT 26 /* Create PLT entry */ +#define R_ALPHA_RELATIVE 27 /* Adjust by program base */ +#define R_ALPHA_TLS_GD_HI 28 +#define R_ALPHA_TLSGD 29 +#define R_ALPHA_TLS_LDM 30 +#define R_ALPHA_DTPMOD64 31 +#define R_ALPHA_GOTDTPREL 32 +#define R_ALPHA_DTPREL64 33 +#define R_ALPHA_DTPRELHI 34 +#define R_ALPHA_DTPRELLO 35 +#define R_ALPHA_DTPREL16 36 +#define R_ALPHA_GOTTPREL 37 +#define R_ALPHA_TPREL64 38 +#define R_ALPHA_TPRELHI 39 +#define R_ALPHA_TPRELLO 40 +#define R_ALPHA_TPREL16 41 +/* Keep this the last entry. */ +#define R_ALPHA_NUM 46 + +/* Magic values of the LITUSE relocation addend. */ +#define LITUSE_ALPHA_ADDR 0 +#define LITUSE_ALPHA_BASE 1 +#define LITUSE_ALPHA_BYTOFF 2 +#define LITUSE_ALPHA_JSR 3 +#define LITUSE_ALPHA_TLS_GD 4 +#define LITUSE_ALPHA_TLS_LDM 5 + +/* Legal values for d_tag of Elf64_Dyn. */ +#define DT_ALPHA_PLTRO (DT_LOPROC + 0) +#define DT_ALPHA_NUM 1 + +/* PowerPC specific declarations */ + +/* Values for Elf32/64_Ehdr.e_flags. */ +#define EF_PPC_EMB 0x80000000 /* PowerPC embedded flag */ + +/* Cygnus local bits below */ +#define EF_PPC_RELOCATABLE 0x00010000 /* PowerPC -mrelocatable flag*/ +#define EF_PPC_RELOCATABLE_LIB 0x00008000 /* PowerPC -mrelocatable-lib + flag */ + +/* PowerPC relocations defined by the ABIs */ +#define R_PPC_NONE 0 +#define R_PPC_ADDR32 1 /* 32bit absolute address */ +#define R_PPC_ADDR24 2 /* 26bit address, 2 bits ignored. */ +#define R_PPC_ADDR16 3 /* 16bit absolute address */ +#define R_PPC_ADDR16_LO 4 /* lower 16bit of absolute address */ +#define R_PPC_ADDR16_HI 5 /* high 16bit of absolute address */ +#define R_PPC_ADDR16_HA 6 /* adjusted high 16bit */ +#define R_PPC_ADDR14 7 /* 16bit address, 2 bits ignored */ +#define R_PPC_ADDR14_BRTAKEN 8 +#define R_PPC_ADDR14_BRNTAKEN 9 +#define R_PPC_REL24 10 /* PC relative 26 bit */ +#define R_PPC_REL14 11 /* PC relative 16 bit */ +#define R_PPC_REL14_BRTAKEN 12 +#define R_PPC_REL14_BRNTAKEN 13 +#define R_PPC_GOT16 14 +#define R_PPC_GOT16_LO 15 +#define R_PPC_GOT16_HI 16 +#define R_PPC_GOT16_HA 17 +#define R_PPC_PLTREL24 18 +#define R_PPC_COPY 19 +#define R_PPC_GLOB_DAT 20 +#define R_PPC_JMP_SLOT 21 +#define R_PPC_RELATIVE 22 +#define R_PPC_LOCAL24PC 23 +#define R_PPC_UADDR32 24 +#define R_PPC_UADDR16 25 +#define R_PPC_REL32 26 +#define R_PPC_PLT32 27 +#define R_PPC_PLTREL32 28 +#define R_PPC_PLT16_LO 29 +#define R_PPC_PLT16_HI 30 +#define R_PPC_PLT16_HA 31 +#define R_PPC_SDAREL16 32 +#define R_PPC_SECTOFF 33 +#define R_PPC_SECTOFF_LO 34 +#define R_PPC_SECTOFF_HI 35 +#define R_PPC_SECTOFF_HA 36 + +/* PowerPC relocations defined for the TLS access ABI. */ +#define R_PPC_TLS 67 /* none (sym+add)@tls */ +#define R_PPC_DTPMOD32 68 /* word32 (sym+add)@dtpmod */ +#define R_PPC_TPREL16 69 /* half16* (sym+add)@tprel */ +#define R_PPC_TPREL16_LO 70 /* half16 (sym+add)@tprel@l */ +#define R_PPC_TPREL16_HI 71 /* half16 (sym+add)@tprel@h */ +#define R_PPC_TPREL16_HA 72 /* half16 (sym+add)@tprel@ha */ +#define R_PPC_TPREL32 73 /* word32 (sym+add)@tprel */ +#define R_PPC_DTPREL16 74 /* half16* (sym+add)@dtprel */ +#define R_PPC_DTPREL16_LO 75 /* half16 (sym+add)@dtprel@l */ +#define R_PPC_DTPREL16_HI 76 /* half16 (sym+add)@dtprel@h */ +#define R_PPC_DTPREL16_HA 77 /* half16 (sym+add)@dtprel@ha */ +#define R_PPC_DTPREL32 78 /* word32 (sym+add)@dtprel */ +#define R_PPC_GOT_TLSGD16 79 /* half16* (sym+add)@got@tlsgd */ +#define R_PPC_GOT_TLSGD16_LO 80 /* half16 (sym+add)@got@tlsgd@l */ +#define R_PPC_GOT_TLSGD16_HI 81 /* half16 (sym+add)@got@tlsgd@h */ +#define R_PPC_GOT_TLSGD16_HA 82 /* half16 (sym+add)@got@tlsgd@ha */ +#define R_PPC_GOT_TLSLD16 83 /* half16* (sym+add)@got@tlsld */ +#define R_PPC_GOT_TLSLD16_LO 84 /* half16 (sym+add)@got@tlsld@l */ +#define R_PPC_GOT_TLSLD16_HI 85 /* half16 (sym+add)@got@tlsld@h */ +#define R_PPC_GOT_TLSLD16_HA 86 /* half16 (sym+add)@got@tlsld@ha */ +#define R_PPC_GOT_TPREL16 87 /* half16* (sym+add)@got@tprel */ +#define R_PPC_GOT_TPREL16_LO 88 /* half16 (sym+add)@got@tprel@l */ +#define R_PPC_GOT_TPREL16_HI 89 /* half16 (sym+add)@got@tprel@h */ +#define R_PPC_GOT_TPREL16_HA 90 /* half16 (sym+add)@got@tprel@ha */ +#define R_PPC_GOT_DTPREL16 91 /* half16* (sym+add)@got@dtprel */ +#define R_PPC_GOT_DTPREL16_LO 92 /* half16* (sym+add)@got@dtprel@l */ +#define R_PPC_GOT_DTPREL16_HI 93 /* half16* (sym+add)@got@dtprel@h */ +#define R_PPC_GOT_DTPREL16_HA 94 /* half16* (sym+add)@got@dtprel@ha */ +#define R_PPC_TLSGD 95 /* none (sym+add)@tlsgd */ +#define R_PPC_TLSLD 96 /* none (sym+add)@tlsld */ + +/* The remaining relocs are from the Embedded ELF ABI, and are not + in the SVR4 ELF ABI. */ +#define R_PPC_EMB_NADDR32 101 +#define R_PPC_EMB_NADDR16 102 +#define R_PPC_EMB_NADDR16_LO 103 +#define R_PPC_EMB_NADDR16_HI 104 +#define R_PPC_EMB_NADDR16_HA 105 +#define R_PPC_EMB_SDAI16 106 +#define R_PPC_EMB_SDA2I16 107 +#define R_PPC_EMB_SDA2REL 108 +#define R_PPC_EMB_SDA21 109 /* 16 bit offset in SDA */ +#define R_PPC_EMB_MRKREF 110 +#define R_PPC_EMB_RELSEC16 111 +#define R_PPC_EMB_RELST_LO 112 +#define R_PPC_EMB_RELST_HI 113 +#define R_PPC_EMB_RELST_HA 114 +#define R_PPC_EMB_BIT_FLD 115 +#define R_PPC_EMB_RELSDA 116 /* 16 bit relative offset in SDA */ + +/* Diab tool relocations. */ +#define R_PPC_DIAB_SDA21_LO 180 /* like EMB_SDA21, but lower 16 bit */ +#define R_PPC_DIAB_SDA21_HI 181 /* like EMB_SDA21, but high 16 bit */ +#define R_PPC_DIAB_SDA21_HA 182 /* like EMB_SDA21, adjusted high 16 */ +#define R_PPC_DIAB_RELSDA_LO 183 /* like EMB_RELSDA, but lower 16 bit */ +#define R_PPC_DIAB_RELSDA_HI 184 /* like EMB_RELSDA, but high 16 bit */ +#define R_PPC_DIAB_RELSDA_HA 185 /* like EMB_RELSDA, adjusted high 16 */ + +/* GNU extension to support local ifunc. */ +#define R_PPC_IRELATIVE 248 + +/* GNU relocs used in PIC code sequences. */ +#define R_PPC_REL16 249 /* half16 (sym+add-.) */ +#define R_PPC_REL16_LO 250 /* half16 (sym+add-.)@l */ +#define R_PPC_REL16_HI 251 /* half16 (sym+add-.)@h */ +#define R_PPC_REL16_HA 252 /* half16 (sym+add-.)@ha */ + +/* This is a phony reloc to handle any old fashioned TOC16 references + that may still be in object files. */ +#define R_PPC_TOC16 255 + +/* PowerPC specific values for the Dyn d_tag field. */ +#define DT_PPC_GOT (DT_LOPROC + 0) +#define DT_PPC_OPT (DT_LOPROC + 1) +#define DT_PPC_NUM 2 + +/* PowerPC specific values for the DT_PPC_OPT Dyn entry. */ +#define PPC_OPT_TLS 1 + +/* PowerPC64 relocations defined by the ABIs */ +#define R_PPC64_NONE R_PPC_NONE +#define R_PPC64_ADDR32 R_PPC_ADDR32 /* 32bit absolute address */ +#define R_PPC64_ADDR24 R_PPC_ADDR24 /* 26bit address, word aligned */ +#define R_PPC64_ADDR16 R_PPC_ADDR16 /* 16bit absolute address */ +#define R_PPC64_ADDR16_LO R_PPC_ADDR16_LO /* lower 16bits of address */ +#define R_PPC64_ADDR16_HI R_PPC_ADDR16_HI /* high 16bits of address. */ +#define R_PPC64_ADDR16_HA R_PPC_ADDR16_HA /* adjusted high 16bits. */ +#define R_PPC64_ADDR14 R_PPC_ADDR14 /* 16bit address, word aligned */ +#define R_PPC64_ADDR14_BRTAKEN R_PPC_ADDR14_BRTAKEN +#define R_PPC64_ADDR14_BRNTAKEN R_PPC_ADDR14_BRNTAKEN +#define R_PPC64_REL24 R_PPC_REL24 /* PC-rel. 26 bit, word aligned */ +#define R_PPC64_REL14 R_PPC_REL14 /* PC relative 16 bit */ +#define R_PPC64_REL14_BRTAKEN R_PPC_REL14_BRTAKEN +#define R_PPC64_REL14_BRNTAKEN R_PPC_REL14_BRNTAKEN +#define R_PPC64_GOT16 R_PPC_GOT16 +#define R_PPC64_GOT16_LO R_PPC_GOT16_LO +#define R_PPC64_GOT16_HI R_PPC_GOT16_HI +#define R_PPC64_GOT16_HA R_PPC_GOT16_HA + +#define R_PPC64_COPY R_PPC_COPY +#define R_PPC64_GLOB_DAT R_PPC_GLOB_DAT +#define R_PPC64_JMP_SLOT R_PPC_JMP_SLOT +#define R_PPC64_RELATIVE R_PPC_RELATIVE + +#define R_PPC64_UADDR32 R_PPC_UADDR32 +#define R_PPC64_UADDR16 R_PPC_UADDR16 +#define R_PPC64_REL32 R_PPC_REL32 +#define R_PPC64_PLT32 R_PPC_PLT32 +#define R_PPC64_PLTREL32 R_PPC_PLTREL32 +#define R_PPC64_PLT16_LO R_PPC_PLT16_LO +#define R_PPC64_PLT16_HI R_PPC_PLT16_HI +#define R_PPC64_PLT16_HA R_PPC_PLT16_HA + +#define R_PPC64_SECTOFF R_PPC_SECTOFF +#define R_PPC64_SECTOFF_LO R_PPC_SECTOFF_LO +#define R_PPC64_SECTOFF_HI R_PPC_SECTOFF_HI +#define R_PPC64_SECTOFF_HA R_PPC_SECTOFF_HA +#define R_PPC64_ADDR30 37 /* word30 (S + A - P) >> 2 */ +#define R_PPC64_ADDR64 38 /* doubleword64 S + A */ +#define R_PPC64_ADDR16_HIGHER 39 /* half16 #higher(S + A) */ +#define R_PPC64_ADDR16_HIGHERA 40 /* half16 #highera(S + A) */ +#define R_PPC64_ADDR16_HIGHEST 41 /* half16 #highest(S + A) */ +#define R_PPC64_ADDR16_HIGHESTA 42 /* half16 #highesta(S + A) */ +#define R_PPC64_UADDR64 43 /* doubleword64 S + A */ +#define R_PPC64_REL64 44 /* doubleword64 S + A - P */ +#define R_PPC64_PLT64 45 /* doubleword64 L + A */ +#define R_PPC64_PLTREL64 46 /* doubleword64 L + A - P */ +#define R_PPC64_TOC16 47 /* half16* S + A - .TOC */ +#define R_PPC64_TOC16_LO 48 /* half16 #lo(S + A - .TOC.) */ +#define R_PPC64_TOC16_HI 49 /* half16 #hi(S + A - .TOC.) */ +#define R_PPC64_TOC16_HA 50 /* half16 #ha(S + A - .TOC.) */ +#define R_PPC64_TOC 51 /* doubleword64 .TOC */ +#define R_PPC64_PLTGOT16 52 /* half16* M + A */ +#define R_PPC64_PLTGOT16_LO 53 /* half16 #lo(M + A) */ +#define R_PPC64_PLTGOT16_HI 54 /* half16 #hi(M + A) */ +#define R_PPC64_PLTGOT16_HA 55 /* half16 #ha(M + A) */ + +#define R_PPC64_ADDR16_DS 56 /* half16ds* (S + A) >> 2 */ +#define R_PPC64_ADDR16_LO_DS 57 /* half16ds #lo(S + A) >> 2 */ +#define R_PPC64_GOT16_DS 58 /* half16ds* (G + A) >> 2 */ +#define R_PPC64_GOT16_LO_DS 59 /* half16ds #lo(G + A) >> 2 */ +#define R_PPC64_PLT16_LO_DS 60 /* half16ds #lo(L + A) >> 2 */ +#define R_PPC64_SECTOFF_DS 61 /* half16ds* (R + A) >> 2 */ +#define R_PPC64_SECTOFF_LO_DS 62 /* half16ds #lo(R + A) >> 2 */ +#define R_PPC64_TOC16_DS 63 /* half16ds* (S + A - .TOC.) >> 2 */ +#define R_PPC64_TOC16_LO_DS 64 /* half16ds #lo(S + A - .TOC.) >> 2 */ +#define R_PPC64_PLTGOT16_DS 65 /* half16ds* (M + A) >> 2 */ +#define R_PPC64_PLTGOT16_LO_DS 66 /* half16ds #lo(M + A) >> 2 */ + +/* PowerPC64 relocations defined for the TLS access ABI. */ +#define R_PPC64_TLS 67 /* none (sym+add)@tls */ +#define R_PPC64_DTPMOD64 68 /* doubleword64 (sym+add)@dtpmod */ +#define R_PPC64_TPREL16 69 /* half16* (sym+add)@tprel */ +#define R_PPC64_TPREL16_LO 70 /* half16 (sym+add)@tprel@l */ +#define R_PPC64_TPREL16_HI 71 /* half16 (sym+add)@tprel@h */ +#define R_PPC64_TPREL16_HA 72 /* half16 (sym+add)@tprel@ha */ +#define R_PPC64_TPREL64 73 /* doubleword64 (sym+add)@tprel */ +#define R_PPC64_DTPREL16 74 /* half16* (sym+add)@dtprel */ +#define R_PPC64_DTPREL16_LO 75 /* half16 (sym+add)@dtprel@l */ +#define R_PPC64_DTPREL16_HI 76 /* half16 (sym+add)@dtprel@h */ +#define R_PPC64_DTPREL16_HA 77 /* half16 (sym+add)@dtprel@ha */ +#define R_PPC64_DTPREL64 78 /* doubleword64 (sym+add)@dtprel */ +#define R_PPC64_GOT_TLSGD16 79 /* half16* (sym+add)@got@tlsgd */ +#define R_PPC64_GOT_TLSGD16_LO 80 /* half16 (sym+add)@got@tlsgd@l */ +#define R_PPC64_GOT_TLSGD16_HI 81 /* half16 (sym+add)@got@tlsgd@h */ +#define R_PPC64_GOT_TLSGD16_HA 82 /* half16 (sym+add)@got@tlsgd@ha */ +#define R_PPC64_GOT_TLSLD16 83 /* half16* (sym+add)@got@tlsld */ +#define R_PPC64_GOT_TLSLD16_LO 84 /* half16 (sym+add)@got@tlsld@l */ +#define R_PPC64_GOT_TLSLD16_HI 85 /* half16 (sym+add)@got@tlsld@h */ +#define R_PPC64_GOT_TLSLD16_HA 86 /* half16 (sym+add)@got@tlsld@ha */ +#define R_PPC64_GOT_TPREL16_DS 87 /* half16ds* (sym+add)@got@tprel */ +#define R_PPC64_GOT_TPREL16_LO_DS 88 /* half16ds (sym+add)@got@tprel@l */ +#define R_PPC64_GOT_TPREL16_HI 89 /* half16 (sym+add)@got@tprel@h */ +#define R_PPC64_GOT_TPREL16_HA 90 /* half16 (sym+add)@got@tprel@ha */ +#define R_PPC64_GOT_DTPREL16_DS 91 /* half16ds* (sym+add)@got@dtprel */ +#define R_PPC64_GOT_DTPREL16_LO_DS 92 /* half16ds (sym+add)@got@dtprel@l */ +#define R_PPC64_GOT_DTPREL16_HI 93 /* half16 (sym+add)@got@dtprel@h */ +#define R_PPC64_GOT_DTPREL16_HA 94 /* half16 (sym+add)@got@dtprel@ha */ +#define R_PPC64_TPREL16_DS 95 /* half16ds* (sym+add)@tprel */ +#define R_PPC64_TPREL16_LO_DS 96 /* half16ds (sym+add)@tprel@l */ +#define R_PPC64_TPREL16_HIGHER 97 /* half16 (sym+add)@tprel@higher */ +#define R_PPC64_TPREL16_HIGHERA 98 /* half16 (sym+add)@tprel@highera */ +#define R_PPC64_TPREL16_HIGHEST 99 /* half16 (sym+add)@tprel@highest */ +#define R_PPC64_TPREL16_HIGHESTA 100 /* half16 (sym+add)@tprel@highesta */ +#define R_PPC64_DTPREL16_DS 101 /* half16ds* (sym+add)@dtprel */ +#define R_PPC64_DTPREL16_LO_DS 102 /* half16ds (sym+add)@dtprel@l */ +#define R_PPC64_DTPREL16_HIGHER 103 /* half16 (sym+add)@dtprel@higher */ +#define R_PPC64_DTPREL16_HIGHERA 104 /* half16 (sym+add)@dtprel@highera */ +#define R_PPC64_DTPREL16_HIGHEST 105 /* half16 (sym+add)@dtprel@highest */ +#define R_PPC64_DTPREL16_HIGHESTA 106 /* half16 (sym+add)@dtprel@highesta */ +#define R_PPC64_TLSGD 107 /* none (sym+add)@tlsgd */ +#define R_PPC64_TLSLD 108 /* none (sym+add)@tlsld */ +#define R_PPC64_TOCSAVE 109 /* none */ + +/* Added when HA and HI relocs were changed to report overflows. */ +#define R_PPC64_ADDR16_HIGH 110 +#define R_PPC64_ADDR16_HIGHA 111 +#define R_PPC64_TPREL16_HIGH 112 +#define R_PPC64_TPREL16_HIGHA 113 +#define R_PPC64_DTPREL16_HIGH 114 +#define R_PPC64_DTPREL16_HIGHA 115 + +/* GNU extension to support local ifunc. */ +#define R_PPC64_JMP_IREL 247 +#define R_PPC64_IRELATIVE 248 +#define R_PPC64_REL16 249 /* half16 (sym+add-.) */ +#define R_PPC64_REL16_LO 250 /* half16 (sym+add-.)@l */ +#define R_PPC64_REL16_HI 251 /* half16 (sym+add-.)@h */ +#define R_PPC64_REL16_HA 252 /* half16 (sym+add-.)@ha */ + +/* e_flags bits specifying ABI. + 1 for original function descriptor using ABI, + 2 for revised ABI without function descriptors, + 0 for unspecified or not using any features affected by the differences. */ +#define EF_PPC64_ABI 3 + +/* PowerPC64 specific values for the Dyn d_tag field. */ +#define DT_PPC64_GLINK (DT_LOPROC + 0) +#define DT_PPC64_OPD (DT_LOPROC + 1) +#define DT_PPC64_OPDSZ (DT_LOPROC + 2) +#define DT_PPC64_OPT (DT_LOPROC + 3) +#define DT_PPC64_NUM 4 + +/* PowerPC64 specific bits in the DT_PPC64_OPT Dyn entry. */ +#define PPC64_OPT_TLS 1 +#define PPC64_OPT_MULTI_TOC 2 +#define PPC64_OPT_LOCALENTRY 4 + +/* PowerPC64 specific values for the Elf64_Sym st_other field. */ +#define STO_PPC64_LOCAL_BIT 5 +#define STO_PPC64_LOCAL_MASK (7 << STO_PPC64_LOCAL_BIT) +#define PPC64_LOCAL_ENTRY_OFFSET(other) \ + (((1 << (((other) & STO_PPC64_LOCAL_MASK) >> STO_PPC64_LOCAL_BIT)) >> 2) << 2) + + +/* ARM specific declarations */ + +/* Processor specific flags for the ELF header e_flags field. */ +#define EF_ARM_RELEXEC 0x01 +#define EF_ARM_HASENTRY 0x02 +#define EF_ARM_INTERWORK 0x04 +#define EF_ARM_APCS_26 0x08 +#define EF_ARM_APCS_FLOAT 0x10 +#define EF_ARM_PIC 0x20 +#define EF_ARM_ALIGN8 0x40 /* 8-bit structure alignment is in use */ +#define EF_ARM_NEW_ABI 0x80 +#define EF_ARM_OLD_ABI 0x100 +#define EF_ARM_SOFT_FLOAT 0x200 +#define EF_ARM_VFP_FLOAT 0x400 +#define EF_ARM_MAVERICK_FLOAT 0x800 + +#define EF_ARM_ABI_FLOAT_SOFT 0x200 /* NB conflicts with EF_ARM_SOFT_FLOAT */ +#define EF_ARM_ABI_FLOAT_HARD 0x400 /* NB conflicts with EF_ARM_VFP_FLOAT */ + + +/* Other constants defined in the ARM ELF spec. version B-01. */ +/* NB. These conflict with values defined above. */ +#define EF_ARM_SYMSARESORTED 0x04 +#define EF_ARM_DYNSYMSUSESEGIDX 0x08 +#define EF_ARM_MAPSYMSFIRST 0x10 +#define EF_ARM_EABIMASK 0XFF000000 + +/* Constants defined in AAELF. */ +#define EF_ARM_BE8 0x00800000 +#define EF_ARM_LE8 0x00400000 + +#define EF_ARM_EABI_VERSION(flags) ((flags) & EF_ARM_EABIMASK) +#define EF_ARM_EABI_UNKNOWN 0x00000000 +#define EF_ARM_EABI_VER1 0x01000000 +#define EF_ARM_EABI_VER2 0x02000000 +#define EF_ARM_EABI_VER3 0x03000000 +#define EF_ARM_EABI_VER4 0x04000000 +#define EF_ARM_EABI_VER5 0x05000000 + +/* Additional symbol types for Thumb. */ +#define STT_ARM_TFUNC STT_LOPROC /* A Thumb function. */ +#define STT_ARM_16BIT STT_HIPROC /* A Thumb label. */ + +/* ARM-specific values for sh_flags */ +#define SHF_ARM_ENTRYSECT 0x10000000 /* Section contains an entry point */ +#define SHF_ARM_COMDEF 0x80000000 /* Section may be multiply defined + in the input to a link step. */ + +/* ARM-specific program header flags */ +#define PF_ARM_SB 0x10000000 /* Segment contains the location + addressed by the static base. */ +#define PF_ARM_PI 0x20000000 /* Position-independent segment. */ +#define PF_ARM_ABS 0x40000000 /* Absolute segment. */ + +/* Processor specific values for the Phdr p_type field. */ +#define PT_ARM_EXIDX (PT_LOPROC + 1) /* ARM unwind segment. */ + +/* Processor specific values for the Shdr sh_type field. */ +#define SHT_ARM_EXIDX (SHT_LOPROC + 1) /* ARM unwind section. */ +#define SHT_ARM_PREEMPTMAP (SHT_LOPROC + 2) /* Preemption details. */ +#define SHT_ARM_ATTRIBUTES (SHT_LOPROC + 3) /* ARM attributes section. */ + + +/* AArch64 relocs. */ + +#define R_AARCH64_NONE 0 /* No relocation. */ + +/* ILP32 AArch64 relocs. */ +#define R_AARCH64_P32_ABS32 1 /* Direct 32 bit. */ +#define R_AARCH64_P32_COPY 180 /* Copy symbol at runtime. */ +#define R_AARCH64_P32_GLOB_DAT 181 /* Create GOT entry. */ +#define R_AARCH64_P32_JUMP_SLOT 182 /* Create PLT entry. */ +#define R_AARCH64_P32_RELATIVE 183 /* Adjust by program base. */ +#define R_AARCH64_P32_TLS_DTPMOD 184 /* Module number, 32 bit. */ +#define R_AARCH64_P32_TLS_DTPREL 185 /* Module-relative offset, 32 bit. */ +#define R_AARCH64_P32_TLS_TPREL 186 /* TP-relative offset, 32 bit. */ +#define R_AARCH64_P32_TLSDESC 187 /* TLS Descriptor. */ +#define R_AARCH64_P32_IRELATIVE 188 /* STT_GNU_IFUNC relocation. */ + +/* LP64 AArch64 relocs. */ +#define R_AARCH64_ABS64 257 /* Direct 64 bit. */ +#define R_AARCH64_ABS32 258 /* Direct 32 bit. */ +#define R_AARCH64_ABS16 259 /* Direct 16-bit. */ +#define R_AARCH64_PREL64 260 /* PC-relative 64-bit. */ +#define R_AARCH64_PREL32 261 /* PC-relative 32-bit. */ +#define R_AARCH64_PREL16 262 /* PC-relative 16-bit. */ +#define R_AARCH64_MOVW_UABS_G0 263 /* Dir. MOVZ imm. from bits 15:0. */ +#define R_AARCH64_MOVW_UABS_G0_NC 264 /* Likewise for MOVK; no check. */ +#define R_AARCH64_MOVW_UABS_G1 265 /* Dir. MOVZ imm. from bits 31:16. */ +#define R_AARCH64_MOVW_UABS_G1_NC 266 /* Likewise for MOVK; no check. */ +#define R_AARCH64_MOVW_UABS_G2 267 /* Dir. MOVZ imm. from bits 47:32. */ +#define R_AARCH64_MOVW_UABS_G2_NC 268 /* Likewise for MOVK; no check. */ +#define R_AARCH64_MOVW_UABS_G3 269 /* Dir. MOV{K,Z} imm. from 63:48. */ +#define R_AARCH64_MOVW_SABS_G0 270 /* Dir. MOV{N,Z} imm. from 15:0. */ +#define R_AARCH64_MOVW_SABS_G1 271 /* Dir. MOV{N,Z} imm. from 31:16. */ +#define R_AARCH64_MOVW_SABS_G2 272 /* Dir. MOV{N,Z} imm. from 47:32. */ +#define R_AARCH64_LD_PREL_LO19 273 /* PC-rel. LD imm. from bits 20:2. */ +#define R_AARCH64_ADR_PREL_LO21 274 /* PC-rel. ADR imm. from bits 20:0. */ +#define R_AARCH64_ADR_PREL_PG_HI21 275 /* Page-rel. ADRP imm. from 32:12. */ +#define R_AARCH64_ADR_PREL_PG_HI21_NC 276 /* Likewise; no overflow check. */ +#define R_AARCH64_ADD_ABS_LO12_NC 277 /* Dir. ADD imm. from bits 11:0. */ +#define R_AARCH64_LDST8_ABS_LO12_NC 278 /* Likewise for LD/ST; no check. */ +#define R_AARCH64_TSTBR14 279 /* PC-rel. TBZ/TBNZ imm. from 15:2. */ +#define R_AARCH64_CONDBR19 280 /* PC-rel. cond. br. imm. from 20:2. */ +#define R_AARCH64_JUMP26 282 /* PC-rel. B imm. from bits 27:2. */ +#define R_AARCH64_CALL26 283 /* Likewise for CALL. */ +#define R_AARCH64_LDST16_ABS_LO12_NC 284 /* Dir. ADD imm. from bits 11:1. */ +#define R_AARCH64_LDST32_ABS_LO12_NC 285 /* Likewise for bits 11:2. */ +#define R_AARCH64_LDST64_ABS_LO12_NC 286 /* Likewise for bits 11:3. */ +#define R_AARCH64_MOVW_PREL_G0 287 /* PC-rel. MOV{N,Z} imm. from 15:0. */ +#define R_AARCH64_MOVW_PREL_G0_NC 288 /* Likewise for MOVK; no check. */ +#define R_AARCH64_MOVW_PREL_G1 289 /* PC-rel. MOV{N,Z} imm. from 31:16. */ +#define R_AARCH64_MOVW_PREL_G1_NC 290 /* Likewise for MOVK; no check. */ +#define R_AARCH64_MOVW_PREL_G2 291 /* PC-rel. MOV{N,Z} imm. from 47:32. */ +#define R_AARCH64_MOVW_PREL_G2_NC 292 /* Likewise for MOVK; no check. */ +#define R_AARCH64_MOVW_PREL_G3 293 /* PC-rel. MOV{N,Z} imm. from 63:48. */ +#define R_AARCH64_LDST128_ABS_LO12_NC 299 /* Dir. ADD imm. from bits 11:4. */ +#define R_AARCH64_MOVW_GOTOFF_G0 300 /* GOT-rel. off. MOV{N,Z} imm. 15:0. */ +#define R_AARCH64_MOVW_GOTOFF_G0_NC 301 /* Likewise for MOVK; no check. */ +#define R_AARCH64_MOVW_GOTOFF_G1 302 /* GOT-rel. o. MOV{N,Z} imm. 31:16. */ +#define R_AARCH64_MOVW_GOTOFF_G1_NC 303 /* Likewise for MOVK; no check. */ +#define R_AARCH64_MOVW_GOTOFF_G2 304 /* GOT-rel. o. MOV{N,Z} imm. 47:32. */ +#define R_AARCH64_MOVW_GOTOFF_G2_NC 305 /* Likewise for MOVK; no check. */ +#define R_AARCH64_MOVW_GOTOFF_G3 306 /* GOT-rel. o. MOV{N,Z} imm. 63:48. */ +#define R_AARCH64_GOTREL64 307 /* GOT-relative 64-bit. */ +#define R_AARCH64_GOTREL32 308 /* GOT-relative 32-bit. */ +#define R_AARCH64_GOT_LD_PREL19 309 /* PC-rel. GOT off. load imm. 20:2. */ +#define R_AARCH64_LD64_GOTOFF_LO15 310 /* GOT-rel. off. LD/ST imm. 14:3. */ +#define R_AARCH64_ADR_GOT_PAGE 311 /* P-page-rel. GOT off. ADRP 32:12. */ +#define R_AARCH64_LD64_GOT_LO12_NC 312 /* Dir. GOT off. LD/ST imm. 11:3. */ +#define R_AARCH64_LD64_GOTPAGE_LO15 313 /* GOT-page-rel. GOT off. LD/ST 14:3 */ +#define R_AARCH64_TLSGD_ADR_PREL21 512 /* PC-relative ADR imm. 20:0. */ +#define R_AARCH64_TLSGD_ADR_PAGE21 513 /* page-rel. ADRP imm. 32:12. */ +#define R_AARCH64_TLSGD_ADD_LO12_NC 514 /* direct ADD imm. from 11:0. */ +#define R_AARCH64_TLSGD_MOVW_G1 515 /* GOT-rel. MOV{N,Z} 31:16. */ +#define R_AARCH64_TLSGD_MOVW_G0_NC 516 /* GOT-rel. MOVK imm. 15:0. */ +#define R_AARCH64_TLSLD_ADR_PREL21 517 /* Like 512; local dynamic model. */ +#define R_AARCH64_TLSLD_ADR_PAGE21 518 /* Like 513; local dynamic model. */ +#define R_AARCH64_TLSLD_ADD_LO12_NC 519 /* Like 514; local dynamic model. */ +#define R_AARCH64_TLSLD_MOVW_G1 520 /* Like 515; local dynamic model. */ +#define R_AARCH64_TLSLD_MOVW_G0_NC 521 /* Like 516; local dynamic model. */ +#define R_AARCH64_TLSLD_LD_PREL19 522 /* TLS PC-rel. load imm. 20:2. */ +#define R_AARCH64_TLSLD_MOVW_DTPREL_G2 523 /* TLS DTP-rel. MOV{N,Z} 47:32. */ +#define R_AARCH64_TLSLD_MOVW_DTPREL_G1 524 /* TLS DTP-rel. MOV{N,Z} 31:16. */ +#define R_AARCH64_TLSLD_MOVW_DTPREL_G1_NC 525 /* Likewise; MOVK; no check. */ +#define R_AARCH64_TLSLD_MOVW_DTPREL_G0 526 /* TLS DTP-rel. MOV{N,Z} 15:0. */ +#define R_AARCH64_TLSLD_MOVW_DTPREL_G0_NC 527 /* Likewise; MOVK; no check. */ +#define R_AARCH64_TLSLD_ADD_DTPREL_HI12 528 /* DTP-rel. ADD imm. from 23:12. */ +#define R_AARCH64_TLSLD_ADD_DTPREL_LO12 529 /* DTP-rel. ADD imm. from 11:0. */ +#define R_AARCH64_TLSLD_ADD_DTPREL_LO12_NC 530 /* Likewise; no ovfl. check. */ +#define R_AARCH64_TLSLD_LDST8_DTPREL_LO12 531 /* DTP-rel. LD/ST imm. 11:0. */ +#define R_AARCH64_TLSLD_LDST8_DTPREL_LO12_NC 532 /* Likewise; no check. */ +#define R_AARCH64_TLSLD_LDST16_DTPREL_LO12 533 /* DTP-rel. LD/ST imm. 11:1. */ +#define R_AARCH64_TLSLD_LDST16_DTPREL_LO12_NC 534 /* Likewise; no check. */ +#define R_AARCH64_TLSLD_LDST32_DTPREL_LO12 535 /* DTP-rel. LD/ST imm. 11:2. */ +#define R_AARCH64_TLSLD_LDST32_DTPREL_LO12_NC 536 /* Likewise; no check. */ +#define R_AARCH64_TLSLD_LDST64_DTPREL_LO12 537 /* DTP-rel. LD/ST imm. 11:3. */ +#define R_AARCH64_TLSLD_LDST64_DTPREL_LO12_NC 538 /* Likewise; no check. */ +#define R_AARCH64_TLSIE_MOVW_GOTTPREL_G1 539 /* GOT-rel. MOV{N,Z} 31:16. */ +#define R_AARCH64_TLSIE_MOVW_GOTTPREL_G0_NC 540 /* GOT-rel. MOVK 15:0. */ +#define R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21 541 /* Page-rel. ADRP 32:12. */ +#define R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC 542 /* Direct LD off. 11:3. */ +#define R_AARCH64_TLSIE_LD_GOTTPREL_PREL19 543 /* PC-rel. load imm. 20:2. */ +#define R_AARCH64_TLSLE_MOVW_TPREL_G2 544 /* TLS TP-rel. MOV{N,Z} 47:32. */ +#define R_AARCH64_TLSLE_MOVW_TPREL_G1 545 /* TLS TP-rel. MOV{N,Z} 31:16. */ +#define R_AARCH64_TLSLE_MOVW_TPREL_G1_NC 546 /* Likewise; MOVK; no check. */ +#define R_AARCH64_TLSLE_MOVW_TPREL_G0 547 /* TLS TP-rel. MOV{N,Z} 15:0. */ +#define R_AARCH64_TLSLE_MOVW_TPREL_G0_NC 548 /* Likewise; MOVK; no check. */ +#define R_AARCH64_TLSLE_ADD_TPREL_HI12 549 /* TP-rel. ADD imm. 23:12. */ +#define R_AARCH64_TLSLE_ADD_TPREL_LO12 550 /* TP-rel. ADD imm. 11:0. */ +#define R_AARCH64_TLSLE_ADD_TPREL_LO12_NC 551 /* Likewise; no ovfl. check. */ +#define R_AARCH64_TLSLE_LDST8_TPREL_LO12 552 /* TP-rel. LD/ST off. 11:0. */ +#define R_AARCH64_TLSLE_LDST8_TPREL_LO12_NC 553 /* Likewise; no ovfl. check. */ +#define R_AARCH64_TLSLE_LDST16_TPREL_LO12 554 /* TP-rel. LD/ST off. 11:1. */ +#define R_AARCH64_TLSLE_LDST16_TPREL_LO12_NC 555 /* Likewise; no check. */ +#define R_AARCH64_TLSLE_LDST32_TPREL_LO12 556 /* TP-rel. LD/ST off. 11:2. */ +#define R_AARCH64_TLSLE_LDST32_TPREL_LO12_NC 557 /* Likewise; no check. */ +#define R_AARCH64_TLSLE_LDST64_TPREL_LO12 558 /* TP-rel. LD/ST off. 11:3. */ +#define R_AARCH64_TLSLE_LDST64_TPREL_LO12_NC 559 /* Likewise; no check. */ +#define R_AARCH64_TLSDESC_LD_PREL19 560 /* PC-rel. load immediate 20:2. */ +#define R_AARCH64_TLSDESC_ADR_PREL21 561 /* PC-rel. ADR immediate 20:0. */ +#define R_AARCH64_TLSDESC_ADR_PAGE21 562 /* Page-rel. ADRP imm. 32:12. */ +#define R_AARCH64_TLSDESC_LD64_LO12 563 /* Direct LD off. from 11:3. */ +#define R_AARCH64_TLSDESC_ADD_LO12 564 /* Direct ADD imm. from 11:0. */ +#define R_AARCH64_TLSDESC_OFF_G1 565 /* GOT-rel. MOV{N,Z} imm. 31:16. */ +#define R_AARCH64_TLSDESC_OFF_G0_NC 566 /* GOT-rel. MOVK imm. 15:0; no ck. */ +#define R_AARCH64_TLSDESC_LDR 567 /* Relax LDR. */ +#define R_AARCH64_TLSDESC_ADD 568 /* Relax ADD. */ +#define R_AARCH64_TLSDESC_CALL 569 /* Relax BLR. */ +#define R_AARCH64_TLSLE_LDST128_TPREL_LO12 570 /* TP-rel. LD/ST off. 11:4. */ +#define R_AARCH64_TLSLE_LDST128_TPREL_LO12_NC 571 /* Likewise; no check. */ +#define R_AARCH64_TLSLD_LDST128_DTPREL_LO12 572 /* DTP-rel. LD/ST imm. 11:4. */ +#define R_AARCH64_TLSLD_LDST128_DTPREL_LO12_NC 573 /* Likewise; no check. */ +#define R_AARCH64_COPY 1024 /* Copy symbol at runtime. */ +#define R_AARCH64_GLOB_DAT 1025 /* Create GOT entry. */ +#define R_AARCH64_JUMP_SLOT 1026 /* Create PLT entry. */ +#define R_AARCH64_RELATIVE 1027 /* Adjust by program base. */ +#define R_AARCH64_TLS_DTPMOD 1028 /* Module number, 64 bit. */ +#define R_AARCH64_TLS_DTPREL 1029 /* Module-relative offset, 64 bit. */ +#define R_AARCH64_TLS_TPREL 1030 /* TP-relative offset, 64 bit. */ +#define R_AARCH64_TLSDESC 1031 /* TLS Descriptor. */ +#define R_AARCH64_IRELATIVE 1032 /* STT_GNU_IFUNC relocation. */ + +/* ARM relocs. */ + +#define R_ARM_NONE 0 /* No reloc */ +#define R_ARM_PC24 1 /* Deprecated PC relative 26 + bit branch. */ +#define R_ARM_ABS32 2 /* Direct 32 bit */ +#define R_ARM_REL32 3 /* PC relative 32 bit */ +#define R_ARM_PC13 4 +#define R_ARM_ABS16 5 /* Direct 16 bit */ +#define R_ARM_ABS12 6 /* Direct 12 bit */ +#define R_ARM_THM_ABS5 7 /* Direct & 0x7C (LDR, STR). */ +#define R_ARM_ABS8 8 /* Direct 8 bit */ +#define R_ARM_SBREL32 9 +#define R_ARM_THM_PC22 10 /* PC relative 24 bit (Thumb32 BL). */ +#define R_ARM_THM_PC8 11 /* PC relative & 0x3FC + (Thumb16 LDR, ADD, ADR). */ +#define R_ARM_AMP_VCALL9 12 +#define R_ARM_SWI24 13 /* Obsolete static relocation. */ +#define R_ARM_TLS_DESC 13 /* Dynamic relocation. */ +#define R_ARM_THM_SWI8 14 /* Reserved. */ +#define R_ARM_XPC25 15 /* Reserved. */ +#define R_ARM_THM_XPC22 16 /* Reserved. */ +#define R_ARM_TLS_DTPMOD32 17 /* ID of module containing symbol */ +#define R_ARM_TLS_DTPOFF32 18 /* Offset in TLS block */ +#define R_ARM_TLS_TPOFF32 19 /* Offset in static TLS block */ +#define R_ARM_COPY 20 /* Copy symbol at runtime */ +#define R_ARM_GLOB_DAT 21 /* Create GOT entry */ +#define R_ARM_JUMP_SLOT 22 /* Create PLT entry */ +#define R_ARM_RELATIVE 23 /* Adjust by program base */ +#define R_ARM_GOTOFF 24 /* 32 bit offset to GOT */ +#define R_ARM_GOTPC 25 /* 32 bit PC relative offset to GOT */ +#define R_ARM_GOT32 26 /* 32 bit GOT entry */ +#define R_ARM_PLT32 27 /* Deprecated, 32 bit PLT address. */ +#define R_ARM_CALL 28 /* PC relative 24 bit (BL, BLX). */ +#define R_ARM_JUMP24 29 /* PC relative 24 bit + (B, BL). */ +#define R_ARM_THM_JUMP24 30 /* PC relative 24 bit (Thumb32 B.W). */ +#define R_ARM_BASE_ABS 31 /* Adjust by program base. */ +#define R_ARM_ALU_PCREL_7_0 32 /* Obsolete. */ +#define R_ARM_ALU_PCREL_15_8 33 /* Obsolete. */ +#define R_ARM_ALU_PCREL_23_15 34 /* Obsolete. */ +#define R_ARM_LDR_SBREL_11_0 35 /* Deprecated, prog. base relative. */ +#define R_ARM_ALU_SBREL_19_12 36 /* Deprecated, prog. base relative. */ +#define R_ARM_ALU_SBREL_27_20 37 /* Deprecated, prog. base relative. */ +#define R_ARM_TARGET1 38 +#define R_ARM_SBREL31 39 /* Program base relative. */ +#define R_ARM_V4BX 40 +#define R_ARM_TARGET2 41 +#define R_ARM_PREL31 42 /* 32 bit PC relative. */ +#define R_ARM_MOVW_ABS_NC 43 /* Direct 16-bit (MOVW). */ +#define R_ARM_MOVT_ABS 44 /* Direct high 16-bit (MOVT). */ +#define R_ARM_MOVW_PREL_NC 45 /* PC relative 16-bit (MOVW). */ +#define R_ARM_MOVT_PREL 46 /* PC relative (MOVT). */ +#define R_ARM_THM_MOVW_ABS_NC 47 /* Direct 16 bit (Thumb32 MOVW). */ +#define R_ARM_THM_MOVT_ABS 48 /* Direct high 16 bit + (Thumb32 MOVT). */ +#define R_ARM_THM_MOVW_PREL_NC 49 /* PC relative 16 bit + (Thumb32 MOVW). */ +#define R_ARM_THM_MOVT_PREL 50 /* PC relative high 16 bit + (Thumb32 MOVT). */ +#define R_ARM_THM_JUMP19 51 /* PC relative 20 bit + (Thumb32 B.W). */ +#define R_ARM_THM_JUMP6 52 /* PC relative X & 0x7E + (Thumb16 CBZ, CBNZ). */ +#define R_ARM_THM_ALU_PREL_11_0 53 /* PC relative 12 bit + (Thumb32 ADR.W). */ +#define R_ARM_THM_PC12 54 /* PC relative 12 bit + (Thumb32 LDR{D,SB,H,SH}). */ +#define R_ARM_ABS32_NOI 55 /* Direct 32-bit. */ +#define R_ARM_REL32_NOI 56 /* PC relative 32-bit. */ +#define R_ARM_ALU_PC_G0_NC 57 /* PC relative (ADD, SUB). */ +#define R_ARM_ALU_PC_G0 58 /* PC relative (ADD, SUB). */ +#define R_ARM_ALU_PC_G1_NC 59 /* PC relative (ADD, SUB). */ +#define R_ARM_ALU_PC_G1 60 /* PC relative (ADD, SUB). */ +#define R_ARM_ALU_PC_G2 61 /* PC relative (ADD, SUB). */ +#define R_ARM_LDR_PC_G1 62 /* PC relative (LDR,STR,LDRB,STRB). */ +#define R_ARM_LDR_PC_G2 63 /* PC relative (LDR,STR,LDRB,STRB). */ +#define R_ARM_LDRS_PC_G0 64 /* PC relative (STR{D,H}, + LDR{D,SB,H,SH}). */ +#define R_ARM_LDRS_PC_G1 65 /* PC relative (STR{D,H}, + LDR{D,SB,H,SH}). */ +#define R_ARM_LDRS_PC_G2 66 /* PC relative (STR{D,H}, + LDR{D,SB,H,SH}). */ +#define R_ARM_LDC_PC_G0 67 /* PC relative (LDC, STC). */ +#define R_ARM_LDC_PC_G1 68 /* PC relative (LDC, STC). */ +#define R_ARM_LDC_PC_G2 69 /* PC relative (LDC, STC). */ +#define R_ARM_ALU_SB_G0_NC 70 /* Program base relative (ADD,SUB). */ +#define R_ARM_ALU_SB_G0 71 /* Program base relative (ADD,SUB). */ +#define R_ARM_ALU_SB_G1_NC 72 /* Program base relative (ADD,SUB). */ +#define R_ARM_ALU_SB_G1 73 /* Program base relative (ADD,SUB). */ +#define R_ARM_ALU_SB_G2 74 /* Program base relative (ADD,SUB). */ +#define R_ARM_LDR_SB_G0 75 /* Program base relative (LDR, + STR, LDRB, STRB). */ +#define R_ARM_LDR_SB_G1 76 /* Program base relative + (LDR, STR, LDRB, STRB). */ +#define R_ARM_LDR_SB_G2 77 /* Program base relative + (LDR, STR, LDRB, STRB). */ +#define R_ARM_LDRS_SB_G0 78 /* Program base relative + (LDR, STR, LDRB, STRB). */ +#define R_ARM_LDRS_SB_G1 79 /* Program base relative + (LDR, STR, LDRB, STRB). */ +#define R_ARM_LDRS_SB_G2 80 /* Program base relative + (LDR, STR, LDRB, STRB). */ +#define R_ARM_LDC_SB_G0 81 /* Program base relative (LDC,STC). */ +#define R_ARM_LDC_SB_G1 82 /* Program base relative (LDC,STC). */ +#define R_ARM_LDC_SB_G2 83 /* Program base relative (LDC,STC). */ +#define R_ARM_MOVW_BREL_NC 84 /* Program base relative 16 + bit (MOVW). */ +#define R_ARM_MOVT_BREL 85 /* Program base relative high + 16 bit (MOVT). */ +#define R_ARM_MOVW_BREL 86 /* Program base relative 16 + bit (MOVW). */ +#define R_ARM_THM_MOVW_BREL_NC 87 /* Program base relative 16 + bit (Thumb32 MOVW). */ +#define R_ARM_THM_MOVT_BREL 88 /* Program base relative high + 16 bit (Thumb32 MOVT). */ +#define R_ARM_THM_MOVW_BREL 89 /* Program base relative 16 + bit (Thumb32 MOVW). */ +#define R_ARM_TLS_GOTDESC 90 +#define R_ARM_TLS_CALL 91 +#define R_ARM_TLS_DESCSEQ 92 /* TLS relaxation. */ +#define R_ARM_THM_TLS_CALL 93 +#define R_ARM_PLT32_ABS 94 +#define R_ARM_GOT_ABS 95 /* GOT entry. */ +#define R_ARM_GOT_PREL 96 /* PC relative GOT entry. */ +#define R_ARM_GOT_BREL12 97 /* GOT entry relative to GOT + origin (LDR). */ +#define R_ARM_GOTOFF12 98 /* 12 bit, GOT entry relative + to GOT origin (LDR, STR). */ +#define R_ARM_GOTRELAX 99 +#define R_ARM_GNU_VTENTRY 100 +#define R_ARM_GNU_VTINHERIT 101 +#define R_ARM_THM_PC11 102 /* PC relative & 0xFFE (Thumb16 B). */ +#define R_ARM_THM_PC9 103 /* PC relative & 0x1FE + (Thumb16 B/B). */ +#define R_ARM_TLS_GD32 104 /* PC-rel 32 bit for global dynamic + thread local data */ +#define R_ARM_TLS_LDM32 105 /* PC-rel 32 bit for local dynamic + thread local data */ +#define R_ARM_TLS_LDO32 106 /* 32 bit offset relative to TLS + block */ +#define R_ARM_TLS_IE32 107 /* PC-rel 32 bit for GOT entry of + static TLS block offset */ +#define R_ARM_TLS_LE32 108 /* 32 bit offset relative to static + TLS block */ +#define R_ARM_TLS_LDO12 109 /* 12 bit relative to TLS + block (LDR, STR). */ +#define R_ARM_TLS_LE12 110 /* 12 bit relative to static + TLS block (LDR, STR). */ +#define R_ARM_TLS_IE12GP 111 /* 12 bit GOT entry relative + to GOT origin (LDR). */ +#define R_ARM_ME_TOO 128 /* Obsolete. */ +#define R_ARM_THM_TLS_DESCSEQ 129 +#define R_ARM_THM_TLS_DESCSEQ16 129 +#define R_ARM_THM_TLS_DESCSEQ32 130 +#define R_ARM_THM_GOT_BREL12 131 /* GOT entry relative to GOT + origin, 12 bit (Thumb32 LDR). */ +#define R_ARM_IRELATIVE 160 +#define R_ARM_RXPC25 249 +#define R_ARM_RSBREL32 250 +#define R_ARM_THM_RPC22 251 +#define R_ARM_RREL32 252 +#define R_ARM_RABS22 253 +#define R_ARM_RPC24 254 +#define R_ARM_RBASE 255 +/* Keep this the last entry. */ +#define R_ARM_NUM 256 + +/* csky */ +#define R_CKCORE_NONE 0 /* no reloc */ +#define R_CKCORE_ADDR32 1 /* direct 32 bit (S + A) */ +#define R_CKCORE_PCRELIMM8BY4 2 /* disp ((S + A - P) >> 2) & 0xff */ +#define R_CKCORE_PCRELIMM11BY2 3 /* disp ((S + A - P) >> 1) & 0x7ff */ +#define R_CKCORE_PCREL32 5 /* 32-bit rel (S + A - P) */ +#define R_CKCORE_PCRELJSR_IMM11BY2 6 /* disp ((S + A - P) >>1) & 0x7ff */ +#define R_CKCORE_RELATIVE 9 /* 32 bit adjust program base(B + A)*/ +#define R_CKCORE_COPY 10 /* 32 bit adjust by program base */ +#define R_CKCORE_GLOB_DAT 11 /* off between got and sym (S) */ +#define R_CKCORE_JUMP_SLOT 12 /* PLT entry (S) */ +#define R_CKCORE_GOTOFF 13 /* offset to GOT (S + A - GOT) */ +#define R_CKCORE_GOTPC 14 /* PC offset to GOT (GOT + A - P) */ +#define R_CKCORE_GOT32 15 /* 32 bit GOT entry (G) */ +#define R_CKCORE_PLT32 16 /* 32 bit PLT entry (G) */ +#define R_CKCORE_ADDRGOT 17 /* GOT entry in GLOB_DAT (GOT + G) */ +#define R_CKCORE_ADDRPLT 18 /* PLT entry in GLOB_DAT (GOT + G) */ +#define R_CKCORE_PCREL_IMM26BY2 19 /* ((S + A - P) >> 1) & 0x3ffffff */ +#define R_CKCORE_PCREL_IMM16BY2 20 /* disp ((S + A - P) >> 1) & 0xffff */ +#define R_CKCORE_PCREL_IMM16BY4 21 /* disp ((S + A - P) >> 2) & 0xffff */ +#define R_CKCORE_PCREL_IMM10BY2 22 /* disp ((S + A - P) >> 1) & 0x3ff */ +#define R_CKCORE_PCREL_IMM10BY4 23 /* disp ((S + A - P) >> 2) & 0x3ff */ +#define R_CKCORE_ADDR_HI16 24 /* high & low 16 bit ADDR */ + /* ((S + A) >> 16) & 0xffff */ +#define R_CKCORE_ADDR_LO16 25 /* (S + A) & 0xffff */ +#define R_CKCORE_GOTPC_HI16 26 /* high & low 16 bit GOTPC */ + /* ((GOT + A - P) >> 16) & 0xffff */ +#define R_CKCORE_GOTPC_LO16 27 /* (GOT + A - P) & 0xffff */ +#define R_CKCORE_GOTOFF_HI16 28 /* high & low 16 bit GOTOFF */ + /* ((S + A - GOT) >> 16) & 0xffff */ +#define R_CKCORE_GOTOFF_LO16 29 /* (S + A - GOT) & 0xffff */ +#define R_CKCORE_GOT12 30 /* 12 bit disp GOT entry (G) */ +#define R_CKCORE_GOT_HI16 31 /* high & low 16 bit GOT */ + /* (G >> 16) & 0xffff */ +#define R_CKCORE_GOT_LO16 32 /* (G & 0xffff) */ +#define R_CKCORE_PLT12 33 /* 12 bit disp PLT entry (G) */ +#define R_CKCORE_PLT_HI16 34 /* high & low 16 bit PLT */ + /* (G >> 16) & 0xffff */ +#define R_CKCORE_PLT_LO16 35 /* G & 0xffff */ +#define R_CKCORE_ADDRGOT_HI16 36 /* high & low 16 bit ADDRGOT */ + /* (GOT + G * 4) & 0xffff */ +#define R_CKCORE_ADDRGOT_LO16 37 /* (GOT + G * 4) & 0xffff */ +#define R_CKCORE_ADDRPLT_HI16 38 /* high & low 16 bit ADDRPLT */ + /* ((GOT + G * 4) >> 16) & 0xFFFF */ +#define R_CKCORE_ADDRPLT_LO16 39 /* (GOT+G*4) & 0xffff */ +#define R_CKCORE_PCREL_JSR_IMM26BY2 40 /* disp ((S+A-P) >>1) & x3ffffff */ +#define R_CKCORE_TOFFSET_LO16 41 /* (S+A-BTEXT) & 0xffff */ +#define R_CKCORE_DOFFSET_LO16 42 /* (S+A-BTEXT) & 0xffff */ +#define R_CKCORE_PCREL_IMM18BY2 43 /* disp ((S+A-P) >>1) & 0x3ffff */ +#define R_CKCORE_DOFFSET_IMM18 44 /* disp (S+A-BDATA) & 0x3ffff */ +#define R_CKCORE_DOFFSET_IMM18BY2 45 /* disp ((S+A-BDATA)>>1) & 0x3ffff */ +#define R_CKCORE_DOFFSET_IMM18BY4 46 /* disp ((S+A-BDATA)>>2) & 0x3ffff */ +#define R_CKCORE_GOT_IMM18BY4 48 /* disp (G >> 2) */ +#define R_CKCORE_PLT_IMM18BY4 49 /* disp (G >> 2) */ +#define R_CKCORE_PCREL_IMM7BY4 50 /* disp ((S+A-P) >>2) & 0x7f */ +#define R_CKCORE_TLS_LE32 51 /* 32 bit offset to TLS block */ +#define R_CKCORE_TLS_IE32 52 +#define R_CKCORE_TLS_GD32 53 +#define R_CKCORE_TLS_LDM32 54 +#define R_CKCORE_TLS_LDO32 55 +#define R_CKCORE_TLS_DTPMOD32 56 +#define R_CKCORE_TLS_DTPOFF32 57 +#define R_CKCORE_TLS_TPOFF32 58 + +/* IA-64 specific declarations. */ + +/* Processor specific flags for the Ehdr e_flags field. */ +#define EF_IA_64_MASKOS 0x0000000f /* os-specific flags */ +#define EF_IA_64_ABI64 0x00000010 /* 64-bit ABI */ +#define EF_IA_64_ARCH 0xff000000 /* arch. version mask */ + +/* Processor specific values for the Phdr p_type field. */ +#define PT_IA_64_ARCHEXT (PT_LOPROC + 0) /* arch extension bits */ +#define PT_IA_64_UNWIND (PT_LOPROC + 1) /* ia64 unwind bits */ +#define PT_IA_64_HP_OPT_ANOT (PT_LOOS + 0x12) +#define PT_IA_64_HP_HSL_ANOT (PT_LOOS + 0x13) +#define PT_IA_64_HP_STACK (PT_LOOS + 0x14) + +/* Processor specific flags for the Phdr p_flags field. */ +#define PF_IA_64_NORECOV 0x80000000 /* spec insns w/o recovery */ + +/* Processor specific values for the Shdr sh_type field. */ +#define SHT_IA_64_EXT (SHT_LOPROC + 0) /* extension bits */ +#define SHT_IA_64_UNWIND (SHT_LOPROC + 1) /* unwind bits */ + +/* Processor specific flags for the Shdr sh_flags field. */ +#define SHF_IA_64_SHORT 0x10000000 /* section near gp */ +#define SHF_IA_64_NORECOV 0x20000000 /* spec insns w/o recovery */ + +/* Processor specific values for the Dyn d_tag field. */ +#define DT_IA_64_PLT_RESERVE (DT_LOPROC + 0) +#define DT_IA_64_NUM 1 + +/* IA-64 relocations. */ +#define R_IA64_NONE 0x00 /* none */ +#define R_IA64_IMM14 0x21 /* symbol + addend, add imm14 */ +#define R_IA64_IMM22 0x22 /* symbol + addend, add imm22 */ +#define R_IA64_IMM64 0x23 /* symbol + addend, mov imm64 */ +#define R_IA64_DIR32MSB 0x24 /* symbol + addend, data4 MSB */ +#define R_IA64_DIR32LSB 0x25 /* symbol + addend, data4 LSB */ +#define R_IA64_DIR64MSB 0x26 /* symbol + addend, data8 MSB */ +#define R_IA64_DIR64LSB 0x27 /* symbol + addend, data8 LSB */ +#define R_IA64_GPREL22 0x2a /* @gprel(sym + add), add imm22 */ +#define R_IA64_GPREL64I 0x2b /* @gprel(sym + add), mov imm64 */ +#define R_IA64_GPREL32MSB 0x2c /* @gprel(sym + add), data4 MSB */ +#define R_IA64_GPREL32LSB 0x2d /* @gprel(sym + add), data4 LSB */ +#define R_IA64_GPREL64MSB 0x2e /* @gprel(sym + add), data8 MSB */ +#define R_IA64_GPREL64LSB 0x2f /* @gprel(sym + add), data8 LSB */ +#define R_IA64_LTOFF22 0x32 /* @ltoff(sym + add), add imm22 */ +#define R_IA64_LTOFF64I 0x33 /* @ltoff(sym + add), mov imm64 */ +#define R_IA64_PLTOFF22 0x3a /* @pltoff(sym + add), add imm22 */ +#define R_IA64_PLTOFF64I 0x3b /* @pltoff(sym + add), mov imm64 */ +#define R_IA64_PLTOFF64MSB 0x3e /* @pltoff(sym + add), data8 MSB */ +#define R_IA64_PLTOFF64LSB 0x3f /* @pltoff(sym + add), data8 LSB */ +#define R_IA64_FPTR64I 0x43 /* @fptr(sym + add), mov imm64 */ +#define R_IA64_FPTR32MSB 0x44 /* @fptr(sym + add), data4 MSB */ +#define R_IA64_FPTR32LSB 0x45 /* @fptr(sym + add), data4 LSB */ +#define R_IA64_FPTR64MSB 0x46 /* @fptr(sym + add), data8 MSB */ +#define R_IA64_FPTR64LSB 0x47 /* @fptr(sym + add), data8 LSB */ +#define R_IA64_PCREL60B 0x48 /* @pcrel(sym + add), brl */ +#define R_IA64_PCREL21B 0x49 /* @pcrel(sym + add), ptb, call */ +#define R_IA64_PCREL21M 0x4a /* @pcrel(sym + add), chk.s */ +#define R_IA64_PCREL21F 0x4b /* @pcrel(sym + add), fchkf */ +#define R_IA64_PCREL32MSB 0x4c /* @pcrel(sym + add), data4 MSB */ +#define R_IA64_PCREL32LSB 0x4d /* @pcrel(sym + add), data4 LSB */ +#define R_IA64_PCREL64MSB 0x4e /* @pcrel(sym + add), data8 MSB */ +#define R_IA64_PCREL64LSB 0x4f /* @pcrel(sym + add), data8 LSB */ +#define R_IA64_LTOFF_FPTR22 0x52 /* @ltoff(@fptr(s+a)), imm22 */ +#define R_IA64_LTOFF_FPTR64I 0x53 /* @ltoff(@fptr(s+a)), imm64 */ +#define R_IA64_LTOFF_FPTR32MSB 0x54 /* @ltoff(@fptr(s+a)), data4 MSB */ +#define R_IA64_LTOFF_FPTR32LSB 0x55 /* @ltoff(@fptr(s+a)), data4 LSB */ +#define R_IA64_LTOFF_FPTR64MSB 0x56 /* @ltoff(@fptr(s+a)), data8 MSB */ +#define R_IA64_LTOFF_FPTR64LSB 0x57 /* @ltoff(@fptr(s+a)), data8 LSB */ +#define R_IA64_SEGREL32MSB 0x5c /* @segrel(sym + add), data4 MSB */ +#define R_IA64_SEGREL32LSB 0x5d /* @segrel(sym + add), data4 LSB */ +#define R_IA64_SEGREL64MSB 0x5e /* @segrel(sym + add), data8 MSB */ +#define R_IA64_SEGREL64LSB 0x5f /* @segrel(sym + add), data8 LSB */ +#define R_IA64_SECREL32MSB 0x64 /* @secrel(sym + add), data4 MSB */ +#define R_IA64_SECREL32LSB 0x65 /* @secrel(sym + add), data4 LSB */ +#define R_IA64_SECREL64MSB 0x66 /* @secrel(sym + add), data8 MSB */ +#define R_IA64_SECREL64LSB 0x67 /* @secrel(sym + add), data8 LSB */ +#define R_IA64_REL32MSB 0x6c /* data 4 + REL */ +#define R_IA64_REL32LSB 0x6d /* data 4 + REL */ +#define R_IA64_REL64MSB 0x6e /* data 8 + REL */ +#define R_IA64_REL64LSB 0x6f /* data 8 + REL */ +#define R_IA64_LTV32MSB 0x74 /* symbol + addend, data4 MSB */ +#define R_IA64_LTV32LSB 0x75 /* symbol + addend, data4 LSB */ +#define R_IA64_LTV64MSB 0x76 /* symbol + addend, data8 MSB */ +#define R_IA64_LTV64LSB 0x77 /* symbol + addend, data8 LSB */ +#define R_IA64_PCREL21BI 0x79 /* @pcrel(sym + add), 21bit inst */ +#define R_IA64_PCREL22 0x7a /* @pcrel(sym + add), 22bit inst */ +#define R_IA64_PCREL64I 0x7b /* @pcrel(sym + add), 64bit inst */ +#define R_IA64_IPLTMSB 0x80 /* dynamic reloc, imported PLT, MSB */ +#define R_IA64_IPLTLSB 0x81 /* dynamic reloc, imported PLT, LSB */ +#define R_IA64_COPY 0x84 /* copy relocation */ +#define R_IA64_SUB 0x85 /* Addend and symbol difference */ +#define R_IA64_LTOFF22X 0x86 /* LTOFF22, relaxable. */ +#define R_IA64_LDXMOV 0x87 /* Use of LTOFF22X. */ +#define R_IA64_TPREL14 0x91 /* @tprel(sym + add), imm14 */ +#define R_IA64_TPREL22 0x92 /* @tprel(sym + add), imm22 */ +#define R_IA64_TPREL64I 0x93 /* @tprel(sym + add), imm64 */ +#define R_IA64_TPREL64MSB 0x96 /* @tprel(sym + add), data8 MSB */ +#define R_IA64_TPREL64LSB 0x97 /* @tprel(sym + add), data8 LSB */ +#define R_IA64_LTOFF_TPREL22 0x9a /* @ltoff(@tprel(s+a)), imm2 */ +#define R_IA64_DTPMOD64MSB 0xa6 /* @dtpmod(sym + add), data8 MSB */ +#define R_IA64_DTPMOD64LSB 0xa7 /* @dtpmod(sym + add), data8 LSB */ +#define R_IA64_LTOFF_DTPMOD22 0xaa /* @ltoff(@dtpmod(sym + add)), imm22 */ +#define R_IA64_DTPREL14 0xb1 /* @dtprel(sym + add), imm14 */ +#define R_IA64_DTPREL22 0xb2 /* @dtprel(sym + add), imm22 */ +#define R_IA64_DTPREL64I 0xb3 /* @dtprel(sym + add), imm64 */ +#define R_IA64_DTPREL32MSB 0xb4 /* @dtprel(sym + add), data4 MSB */ +#define R_IA64_DTPREL32LSB 0xb5 /* @dtprel(sym + add), data4 LSB */ +#define R_IA64_DTPREL64MSB 0xb6 /* @dtprel(sym + add), data8 MSB */ +#define R_IA64_DTPREL64LSB 0xb7 /* @dtprel(sym + add), data8 LSB */ +#define R_IA64_LTOFF_DTPREL22 0xba /* @ltoff(@dtprel(s+a)), imm22 */ + +/* SH specific declarations */ + +/* Processor specific flags for the ELF header e_flags field. */ +#define EF_SH_MACH_MASK 0x1f +#define EF_SH_UNKNOWN 0x0 +#define EF_SH1 0x1 +#define EF_SH2 0x2 +#define EF_SH3 0x3 +#define EF_SH_DSP 0x4 +#define EF_SH3_DSP 0x5 +#define EF_SH4AL_DSP 0x6 +#define EF_SH3E 0x8 +#define EF_SH4 0x9 +#define EF_SH2E 0xb +#define EF_SH4A 0xc +#define EF_SH2A 0xd +#define EF_SH4_NOFPU 0x10 +#define EF_SH4A_NOFPU 0x11 +#define EF_SH4_NOMMU_NOFPU 0x12 +#define EF_SH2A_NOFPU 0x13 +#define EF_SH3_NOMMU 0x14 +#define EF_SH2A_SH4_NOFPU 0x15 +#define EF_SH2A_SH3_NOFPU 0x16 +#define EF_SH2A_SH4 0x17 +#define EF_SH2A_SH3E 0x18 + +/* SH relocs. */ +#define R_SH_NONE 0 +#define R_SH_DIR32 1 +#define R_SH_REL32 2 +#define R_SH_DIR8WPN 3 +#define R_SH_IND12W 4 +#define R_SH_DIR8WPL 5 +#define R_SH_DIR8WPZ 6 +#define R_SH_DIR8BP 7 +#define R_SH_DIR8W 8 +#define R_SH_DIR8L 9 +#define R_SH_SWITCH16 25 +#define R_SH_SWITCH32 26 +#define R_SH_USES 27 +#define R_SH_COUNT 28 +#define R_SH_ALIGN 29 +#define R_SH_CODE 30 +#define R_SH_DATA 31 +#define R_SH_LABEL 32 +#define R_SH_SWITCH8 33 +#define R_SH_GNU_VTINHERIT 34 +#define R_SH_GNU_VTENTRY 35 +#define R_SH_TLS_GD_32 144 +#define R_SH_TLS_LD_32 145 +#define R_SH_TLS_LDO_32 146 +#define R_SH_TLS_IE_32 147 +#define R_SH_TLS_LE_32 148 +#define R_SH_TLS_DTPMOD32 149 +#define R_SH_TLS_DTPOFF32 150 +#define R_SH_TLS_TPOFF32 151 +#define R_SH_GOT32 160 +#define R_SH_PLT32 161 +#define R_SH_COPY 162 +#define R_SH_GLOB_DAT 163 +#define R_SH_JMP_SLOT 164 +#define R_SH_RELATIVE 165 +#define R_SH_GOTOFF 166 +#define R_SH_GOTPC 167 +/* Keep this the last entry. */ +#define R_SH_NUM 256 + +/* S/390 specific definitions. */ + +/* Valid values for the e_flags field. */ + +#define EF_S390_HIGH_GPRS 0x00000001 /* High GPRs kernel facility needed. */ + +/* Additional s390 relocs */ + +#define R_390_NONE 0 /* No reloc. */ +#define R_390_8 1 /* Direct 8 bit. */ +#define R_390_12 2 /* Direct 12 bit. */ +#define R_390_16 3 /* Direct 16 bit. */ +#define R_390_32 4 /* Direct 32 bit. */ +#define R_390_PC32 5 /* PC relative 32 bit. */ +#define R_390_GOT12 6 /* 12 bit GOT offset. */ +#define R_390_GOT32 7 /* 32 bit GOT offset. */ +#define R_390_PLT32 8 /* 32 bit PC relative PLT address. */ +#define R_390_COPY 9 /* Copy symbol at runtime. */ +#define R_390_GLOB_DAT 10 /* Create GOT entry. */ +#define R_390_JMP_SLOT 11 /* Create PLT entry. */ +#define R_390_RELATIVE 12 /* Adjust by program base. */ +#define R_390_GOTOFF32 13 /* 32 bit offset to GOT. */ +#define R_390_GOTPC 14 /* 32 bit PC relative offset to GOT. */ +#define R_390_GOT16 15 /* 16 bit GOT offset. */ +#define R_390_PC16 16 /* PC relative 16 bit. */ +#define R_390_PC16DBL 17 /* PC relative 16 bit shifted by 1. */ +#define R_390_PLT16DBL 18 /* 16 bit PC rel. PLT shifted by 1. */ +#define R_390_PC32DBL 19 /* PC relative 32 bit shifted by 1. */ +#define R_390_PLT32DBL 20 /* 32 bit PC rel. PLT shifted by 1. */ +#define R_390_GOTPCDBL 21 /* 32 bit PC rel. GOT shifted by 1. */ +#define R_390_64 22 /* Direct 64 bit. */ +#define R_390_PC64 23 /* PC relative 64 bit. */ +#define R_390_GOT64 24 /* 64 bit GOT offset. */ +#define R_390_PLT64 25 /* 64 bit PC relative PLT address. */ +#define R_390_GOTENT 26 /* 32 bit PC rel. to GOT entry >> 1. */ +#define R_390_GOTOFF16 27 /* 16 bit offset to GOT. */ +#define R_390_GOTOFF64 28 /* 64 bit offset to GOT. */ +#define R_390_GOTPLT12 29 /* 12 bit offset to jump slot. */ +#define R_390_GOTPLT16 30 /* 16 bit offset to jump slot. */ +#define R_390_GOTPLT32 31 /* 32 bit offset to jump slot. */ +#define R_390_GOTPLT64 32 /* 64 bit offset to jump slot. */ +#define R_390_GOTPLTENT 33 /* 32 bit rel. offset to jump slot. */ +#define R_390_PLTOFF16 34 /* 16 bit offset from GOT to PLT. */ +#define R_390_PLTOFF32 35 /* 32 bit offset from GOT to PLT. */ +#define R_390_PLTOFF64 36 /* 16 bit offset from GOT to PLT. */ +#define R_390_TLS_LOAD 37 /* Tag for load insn in TLS code. */ +#define R_390_TLS_GDCALL 38 /* Tag for function call in general + dynamic TLS code. */ +#define R_390_TLS_LDCALL 39 /* Tag for function call in local + dynamic TLS code. */ +#define R_390_TLS_GD32 40 /* Direct 32 bit for general dynamic + thread local data. */ +#define R_390_TLS_GD64 41 /* Direct 64 bit for general dynamic + thread local data. */ +#define R_390_TLS_GOTIE12 42 /* 12 bit GOT offset for static TLS + block offset. */ +#define R_390_TLS_GOTIE32 43 /* 32 bit GOT offset for static TLS + block offset. */ +#define R_390_TLS_GOTIE64 44 /* 64 bit GOT offset for static TLS + block offset. */ +#define R_390_TLS_LDM32 45 /* Direct 32 bit for local dynamic + thread local data in LE code. */ +#define R_390_TLS_LDM64 46 /* Direct 64 bit for local dynamic + thread local data in LE code. */ +#define R_390_TLS_IE32 47 /* 32 bit address of GOT entry for + negated static TLS block offset. */ +#define R_390_TLS_IE64 48 /* 64 bit address of GOT entry for + negated static TLS block offset. */ +#define R_390_TLS_IEENT 49 /* 32 bit rel. offset to GOT entry for + negated static TLS block offset. */ +#define R_390_TLS_LE32 50 /* 32 bit negated offset relative to + static TLS block. */ +#define R_390_TLS_LE64 51 /* 64 bit negated offset relative to + static TLS block. */ +#define R_390_TLS_LDO32 52 /* 32 bit offset relative to TLS + block. */ +#define R_390_TLS_LDO64 53 /* 64 bit offset relative to TLS + block. */ +#define R_390_TLS_DTPMOD 54 /* ID of module containing symbol. */ +#define R_390_TLS_DTPOFF 55 /* Offset in TLS block. */ +#define R_390_TLS_TPOFF 56 /* Negated offset in static TLS + block. */ +#define R_390_20 57 /* Direct 20 bit. */ +#define R_390_GOT20 58 /* 20 bit GOT offset. */ +#define R_390_GOTPLT20 59 /* 20 bit offset to jump slot. */ +#define R_390_TLS_GOTIE20 60 /* 20 bit GOT offset for static TLS + block offset. */ +#define R_390_IRELATIVE 61 /* STT_GNU_IFUNC relocation. */ +/* Keep this the last entry. */ +#define R_390_NUM 62 + + +/* CRIS relocations. */ +#define R_CRIS_NONE 0 +#define R_CRIS_8 1 +#define R_CRIS_16 2 +#define R_CRIS_32 3 +#define R_CRIS_8_PCREL 4 +#define R_CRIS_16_PCREL 5 +#define R_CRIS_32_PCREL 6 +#define R_CRIS_GNU_VTINHERIT 7 +#define R_CRIS_GNU_VTENTRY 8 +#define R_CRIS_COPY 9 +#define R_CRIS_GLOB_DAT 10 +#define R_CRIS_JUMP_SLOT 11 +#define R_CRIS_RELATIVE 12 +#define R_CRIS_16_GOT 13 +#define R_CRIS_32_GOT 14 +#define R_CRIS_16_GOTPLT 15 +#define R_CRIS_32_GOTPLT 16 +#define R_CRIS_32_GOTREL 17 +#define R_CRIS_32_PLT_GOTREL 18 +#define R_CRIS_32_PLT_PCREL 19 + +#define R_CRIS_NUM 20 + + +/* AMD x86-64 relocations. */ +#define R_X86_64_NONE 0 /* No reloc */ +#define R_X86_64_64 1 /* Direct 64 bit */ +#define R_X86_64_PC32 2 /* PC relative 32 bit signed */ +#define R_X86_64_GOT32 3 /* 32 bit GOT entry */ +#define R_X86_64_PLT32 4 /* 32 bit PLT address */ +#define R_X86_64_COPY 5 /* Copy symbol at runtime */ +#define R_X86_64_GLOB_DAT 6 /* Create GOT entry */ +#define R_X86_64_JUMP_SLOT 7 /* Create PLT entry */ +#define R_X86_64_RELATIVE 8 /* Adjust by program base */ +#define R_X86_64_GOTPCREL 9 /* 32 bit signed PC relative + offset to GOT */ +#define R_X86_64_32 10 /* Direct 32 bit zero extended */ +#define R_X86_64_32S 11 /* Direct 32 bit sign extended */ +#define R_X86_64_16 12 /* Direct 16 bit zero extended */ +#define R_X86_64_PC16 13 /* 16 bit sign extended pc relative */ +#define R_X86_64_8 14 /* Direct 8 bit sign extended */ +#define R_X86_64_PC8 15 /* 8 bit sign extended pc relative */ +#define R_X86_64_DTPMOD64 16 /* ID of module containing symbol */ +#define R_X86_64_DTPOFF64 17 /* Offset in module's TLS block */ +#define R_X86_64_TPOFF64 18 /* Offset in initial TLS block */ +#define R_X86_64_TLSGD 19 /* 32 bit signed PC relative offset + to two GOT entries for GD symbol */ +#define R_X86_64_TLSLD 20 /* 32 bit signed PC relative offset + to two GOT entries for LD symbol */ +#define R_X86_64_DTPOFF32 21 /* Offset in TLS block */ +#define R_X86_64_GOTTPOFF 22 /* 32 bit signed PC relative offset + to GOT entry for IE symbol */ +#define R_X86_64_TPOFF32 23 /* Offset in initial TLS block */ +#define R_X86_64_PC64 24 /* PC relative 64 bit */ +#define R_X86_64_GOTOFF64 25 /* 64 bit offset to GOT */ +#define R_X86_64_GOTPC32 26 /* 32 bit signed pc relative + offset to GOT */ +#define R_X86_64_GOT64 27 /* 64-bit GOT entry offset */ +#define R_X86_64_GOTPCREL64 28 /* 64-bit PC relative offset + to GOT entry */ +#define R_X86_64_GOTPC64 29 /* 64-bit PC relative offset to GOT */ +#define R_X86_64_GOTPLT64 30 /* like GOT64, says PLT entry needed */ +#define R_X86_64_PLTOFF64 31 /* 64-bit GOT relative offset + to PLT entry */ +#define R_X86_64_SIZE32 32 /* Size of symbol plus 32-bit addend */ +#define R_X86_64_SIZE64 33 /* Size of symbol plus 64-bit addend */ +#define R_X86_64_GOTPC32_TLSDESC 34 /* GOT offset for TLS descriptor. */ +#define R_X86_64_TLSDESC_CALL 35 /* Marker for call through TLS + descriptor. */ +#define R_X86_64_TLSDESC 36 /* TLS descriptor. */ +#define R_X86_64_IRELATIVE 37 /* Adjust indirectly by program base */ +#define R_X86_64_RELATIVE64 38 /* 64-bit adjust by program base */ + /* 39 Reserved was R_X86_64_PC32_BND */ + /* 40 Reserved was R_X86_64_PLT32_BND */ +#define R_X86_64_GOTPCRELX 41 /* Load from 32 bit signed pc relative + offset to GOT entry without REX + prefix, relaxable. */ +#define R_X86_64_REX_GOTPCRELX 42 /* Load from 32 bit signed pc relative + offset to GOT entry with REX prefix, + relaxable. */ +#define R_X86_64_NUM 43 + +/* x86-64 sh_type values. */ +#define SHT_X86_64_UNWIND 0x70000001 /* Unwind information. */ + + +/* AM33 relocations. */ +#define R_MN10300_NONE 0 /* No reloc. */ +#define R_MN10300_32 1 /* Direct 32 bit. */ +#define R_MN10300_16 2 /* Direct 16 bit. */ +#define R_MN10300_8 3 /* Direct 8 bit. */ +#define R_MN10300_PCREL32 4 /* PC-relative 32-bit. */ +#define R_MN10300_PCREL16 5 /* PC-relative 16-bit signed. */ +#define R_MN10300_PCREL8 6 /* PC-relative 8-bit signed. */ +#define R_MN10300_GNU_VTINHERIT 7 /* Ancient C++ vtable garbage... */ +#define R_MN10300_GNU_VTENTRY 8 /* ... collection annotation. */ +#define R_MN10300_24 9 /* Direct 24 bit. */ +#define R_MN10300_GOTPC32 10 /* 32-bit PCrel offset to GOT. */ +#define R_MN10300_GOTPC16 11 /* 16-bit PCrel offset to GOT. */ +#define R_MN10300_GOTOFF32 12 /* 32-bit offset from GOT. */ +#define R_MN10300_GOTOFF24 13 /* 24-bit offset from GOT. */ +#define R_MN10300_GOTOFF16 14 /* 16-bit offset from GOT. */ +#define R_MN10300_PLT32 15 /* 32-bit PCrel to PLT entry. */ +#define R_MN10300_PLT16 16 /* 16-bit PCrel to PLT entry. */ +#define R_MN10300_GOT32 17 /* 32-bit offset to GOT entry. */ +#define R_MN10300_GOT24 18 /* 24-bit offset to GOT entry. */ +#define R_MN10300_GOT16 19 /* 16-bit offset to GOT entry. */ +#define R_MN10300_COPY 20 /* Copy symbol at runtime. */ +#define R_MN10300_GLOB_DAT 21 /* Create GOT entry. */ +#define R_MN10300_JMP_SLOT 22 /* Create PLT entry. */ +#define R_MN10300_RELATIVE 23 /* Adjust by program base. */ +#define R_MN10300_TLS_GD 24 /* 32-bit offset for global dynamic. */ +#define R_MN10300_TLS_LD 25 /* 32-bit offset for local dynamic. */ +#define R_MN10300_TLS_LDO 26 /* Module-relative offset. */ +#define R_MN10300_TLS_GOTIE 27 /* GOT offset for static TLS block + offset. */ +#define R_MN10300_TLS_IE 28 /* GOT address for static TLS block + offset. */ +#define R_MN10300_TLS_LE 29 /* Offset relative to static TLS + block. */ +#define R_MN10300_TLS_DTPMOD 30 /* ID of module containing symbol. */ +#define R_MN10300_TLS_DTPOFF 31 /* Offset in module TLS block. */ +#define R_MN10300_TLS_TPOFF 32 /* Offset in static TLS block. */ +#define R_MN10300_SYM_DIFF 33 /* Adjustment for next reloc as needed + by linker relaxation. */ +#define R_MN10300_ALIGN 34 /* Alignment requirement for linker + relaxation. */ +#define R_MN10300_NUM 35 + + +/* M32R relocs. */ +#define R_M32R_NONE 0 /* No reloc. */ +#define R_M32R_16 1 /* Direct 16 bit. */ +#define R_M32R_32 2 /* Direct 32 bit. */ +#define R_M32R_24 3 /* Direct 24 bit. */ +#define R_M32R_10_PCREL 4 /* PC relative 10 bit shifted. */ +#define R_M32R_18_PCREL 5 /* PC relative 18 bit shifted. */ +#define R_M32R_26_PCREL 6 /* PC relative 26 bit shifted. */ +#define R_M32R_HI16_ULO 7 /* High 16 bit with unsigned low. */ +#define R_M32R_HI16_SLO 8 /* High 16 bit with signed low. */ +#define R_M32R_LO16 9 /* Low 16 bit. */ +#define R_M32R_SDA16 10 /* 16 bit offset in SDA. */ +#define R_M32R_GNU_VTINHERIT 11 +#define R_M32R_GNU_VTENTRY 12 +/* M32R relocs use SHT_RELA. */ +#define R_M32R_16_RELA 33 /* Direct 16 bit. */ +#define R_M32R_32_RELA 34 /* Direct 32 bit. */ +#define R_M32R_24_RELA 35 /* Direct 24 bit. */ +#define R_M32R_10_PCREL_RELA 36 /* PC relative 10 bit shifted. */ +#define R_M32R_18_PCREL_RELA 37 /* PC relative 18 bit shifted. */ +#define R_M32R_26_PCREL_RELA 38 /* PC relative 26 bit shifted. */ +#define R_M32R_HI16_ULO_RELA 39 /* High 16 bit with unsigned low */ +#define R_M32R_HI16_SLO_RELA 40 /* High 16 bit with signed low */ +#define R_M32R_LO16_RELA 41 /* Low 16 bit */ +#define R_M32R_SDA16_RELA 42 /* 16 bit offset in SDA */ +#define R_M32R_RELA_GNU_VTINHERIT 43 +#define R_M32R_RELA_GNU_VTENTRY 44 +#define R_M32R_REL32 45 /* PC relative 32 bit. */ + +#define R_M32R_GOT24 48 /* 24 bit GOT entry */ +#define R_M32R_26_PLTREL 49 /* 26 bit PC relative to PLT shifted */ +#define R_M32R_COPY 50 /* Copy symbol at runtime */ +#define R_M32R_GLOB_DAT 51 /* Create GOT entry */ +#define R_M32R_JMP_SLOT 52 /* Create PLT entry */ +#define R_M32R_RELATIVE 53 /* Adjust by program base */ +#define R_M32R_GOTOFF 54 /* 24 bit offset to GOT */ +#define R_M32R_GOTPC24 55 /* 24 bit PC relative offset to GOT */ +#define R_M32R_GOT16_HI_ULO 56 /* High 16 bit GOT entry with unsigned + low */ +#define R_M32R_GOT16_HI_SLO 57 /* High 16 bit GOT entry with signed + low */ +#define R_M32R_GOT16_LO 58 /* Low 16 bit GOT entry */ +#define R_M32R_GOTPC_HI_ULO 59 /* High 16 bit PC relative offset to + GOT with unsigned low */ +#define R_M32R_GOTPC_HI_SLO 60 /* High 16 bit PC relative offset to + GOT with signed low */ +#define R_M32R_GOTPC_LO 61 /* Low 16 bit PC relative offset to + GOT */ +#define R_M32R_GOTOFF_HI_ULO 62 /* High 16 bit offset to GOT + with unsigned low */ +#define R_M32R_GOTOFF_HI_SLO 63 /* High 16 bit offset to GOT + with signed low */ +#define R_M32R_GOTOFF_LO 64 /* Low 16 bit offset to GOT */ +#define R_M32R_NUM 256 /* Keep this the last entry. */ + +/* MicroBlaze relocations */ +#define R_MICROBLAZE_NONE 0 /* No reloc. */ +#define R_MICROBLAZE_32 1 /* Direct 32 bit. */ +#define R_MICROBLAZE_32_PCREL 2 /* PC relative 32 bit. */ +#define R_MICROBLAZE_64_PCREL 3 /* PC relative 64 bit. */ +#define R_MICROBLAZE_32_PCREL_LO 4 /* Low 16 bits of PCREL32. */ +#define R_MICROBLAZE_64 5 /* Direct 64 bit. */ +#define R_MICROBLAZE_32_LO 6 /* Low 16 bit. */ +#define R_MICROBLAZE_SRO32 7 /* Read-only small data area. */ +#define R_MICROBLAZE_SRW32 8 /* Read-write small data area. */ +#define R_MICROBLAZE_64_NONE 9 /* No reloc. */ +#define R_MICROBLAZE_32_SYM_OP_SYM 10 /* Symbol Op Symbol relocation. */ +#define R_MICROBLAZE_GNU_VTINHERIT 11 /* GNU C++ vtable hierarchy. */ +#define R_MICROBLAZE_GNU_VTENTRY 12 /* GNU C++ vtable member usage. */ +#define R_MICROBLAZE_GOTPC_64 13 /* PC-relative GOT offset. */ +#define R_MICROBLAZE_GOT_64 14 /* GOT entry offset. */ +#define R_MICROBLAZE_PLT_64 15 /* PLT offset (PC-relative). */ +#define R_MICROBLAZE_REL 16 /* Adjust by program base. */ +#define R_MICROBLAZE_JUMP_SLOT 17 /* Create PLT entry. */ +#define R_MICROBLAZE_GLOB_DAT 18 /* Create GOT entry. */ +#define R_MICROBLAZE_GOTOFF_64 19 /* 64 bit offset to GOT. */ +#define R_MICROBLAZE_GOTOFF_32 20 /* 32 bit offset to GOT. */ +#define R_MICROBLAZE_COPY 21 /* Runtime copy. */ +#define R_MICROBLAZE_TLS 22 /* TLS Reloc. */ +#define R_MICROBLAZE_TLSGD 23 /* TLS General Dynamic. */ +#define R_MICROBLAZE_TLSLD 24 /* TLS Local Dynamic. */ +#define R_MICROBLAZE_TLSDTPMOD32 25 /* TLS Module ID. */ +#define R_MICROBLAZE_TLSDTPREL32 26 /* TLS Offset Within TLS Block. */ +#define R_MICROBLAZE_TLSDTPREL64 27 /* TLS Offset Within TLS Block. */ +#define R_MICROBLAZE_TLSGOTTPREL32 28 /* TLS Offset From Thread Pointer. */ +#define R_MICROBLAZE_TLSTPREL32 29 /* TLS Offset From Thread Pointer. */ + +/* Legal values for d_tag (dynamic entry type). */ +#define DT_NIOS2_GP 0x70000002 /* Address of _gp. */ + +/* Nios II relocations. */ +#define R_NIOS2_NONE 0 /* No reloc. */ +#define R_NIOS2_S16 1 /* Direct signed 16 bit. */ +#define R_NIOS2_U16 2 /* Direct unsigned 16 bit. */ +#define R_NIOS2_PCREL16 3 /* PC relative 16 bit. */ +#define R_NIOS2_CALL26 4 /* Direct call. */ +#define R_NIOS2_IMM5 5 /* 5 bit constant expression. */ +#define R_NIOS2_CACHE_OPX 6 /* 5 bit expression, shift 22. */ +#define R_NIOS2_IMM6 7 /* 6 bit constant expression. */ +#define R_NIOS2_IMM8 8 /* 8 bit constant expression. */ +#define R_NIOS2_HI16 9 /* High 16 bit. */ +#define R_NIOS2_LO16 10 /* Low 16 bit. */ +#define R_NIOS2_HIADJ16 11 /* High 16 bit, adjusted. */ +#define R_NIOS2_BFD_RELOC_32 12 /* 32 bit symbol value + addend. */ +#define R_NIOS2_BFD_RELOC_16 13 /* 16 bit symbol value + addend. */ +#define R_NIOS2_BFD_RELOC_8 14 /* 8 bit symbol value + addend. */ +#define R_NIOS2_GPREL 15 /* 16 bit GP pointer offset. */ +#define R_NIOS2_GNU_VTINHERIT 16 /* GNU C++ vtable hierarchy. */ +#define R_NIOS2_GNU_VTENTRY 17 /* GNU C++ vtable member usage. */ +#define R_NIOS2_UJMP 18 /* Unconditional branch. */ +#define R_NIOS2_CJMP 19 /* Conditional branch. */ +#define R_NIOS2_CALLR 20 /* Indirect call through register. */ +#define R_NIOS2_ALIGN 21 /* Alignment requirement for + linker relaxation. */ +#define R_NIOS2_GOT16 22 /* 16 bit GOT entry. */ +#define R_NIOS2_CALL16 23 /* 16 bit GOT entry for function. */ +#define R_NIOS2_GOTOFF_LO 24 /* %lo of offset to GOT pointer. */ +#define R_NIOS2_GOTOFF_HA 25 /* %hiadj of offset to GOT pointer. */ +#define R_NIOS2_PCREL_LO 26 /* %lo of PC relative offset. */ +#define R_NIOS2_PCREL_HA 27 /* %hiadj of PC relative offset. */ +#define R_NIOS2_TLS_GD16 28 /* 16 bit GOT offset for TLS GD. */ +#define R_NIOS2_TLS_LDM16 29 /* 16 bit GOT offset for TLS LDM. */ +#define R_NIOS2_TLS_LDO16 30 /* 16 bit module relative offset. */ +#define R_NIOS2_TLS_IE16 31 /* 16 bit GOT offset for TLS IE. */ +#define R_NIOS2_TLS_LE16 32 /* 16 bit LE TP-relative offset. */ +#define R_NIOS2_TLS_DTPMOD 33 /* Module number. */ +#define R_NIOS2_TLS_DTPREL 34 /* Module-relative offset. */ +#define R_NIOS2_TLS_TPREL 35 /* TP-relative offset. */ +#define R_NIOS2_COPY 36 /* Copy symbol at runtime. */ +#define R_NIOS2_GLOB_DAT 37 /* Create GOT entry. */ +#define R_NIOS2_JUMP_SLOT 38 /* Create PLT entry. */ +#define R_NIOS2_RELATIVE 39 /* Adjust by program base. */ +#define R_NIOS2_GOTOFF 40 /* 16 bit offset to GOT pointer. */ +#define R_NIOS2_CALL26_NOAT 41 /* Direct call in .noat section. */ +#define R_NIOS2_GOT_LO 42 /* %lo() of GOT entry. */ +#define R_NIOS2_GOT_HA 43 /* %hiadj() of GOT entry. */ +#define R_NIOS2_CALL_LO 44 /* %lo() of function GOT entry. */ +#define R_NIOS2_CALL_HA 45 /* %hiadj() of function GOT entry. */ + +/* TILEPro relocations. */ +#define R_TILEPRO_NONE 0 /* No reloc */ +#define R_TILEPRO_32 1 /* Direct 32 bit */ +#define R_TILEPRO_16 2 /* Direct 16 bit */ +#define R_TILEPRO_8 3 /* Direct 8 bit */ +#define R_TILEPRO_32_PCREL 4 /* PC relative 32 bit */ +#define R_TILEPRO_16_PCREL 5 /* PC relative 16 bit */ +#define R_TILEPRO_8_PCREL 6 /* PC relative 8 bit */ +#define R_TILEPRO_LO16 7 /* Low 16 bit */ +#define R_TILEPRO_HI16 8 /* High 16 bit */ +#define R_TILEPRO_HA16 9 /* High 16 bit, adjusted */ +#define R_TILEPRO_COPY 10 /* Copy relocation */ +#define R_TILEPRO_GLOB_DAT 11 /* Create GOT entry */ +#define R_TILEPRO_JMP_SLOT 12 /* Create PLT entry */ +#define R_TILEPRO_RELATIVE 13 /* Adjust by program base */ +#define R_TILEPRO_BROFF_X1 14 /* X1 pipe branch offset */ +#define R_TILEPRO_JOFFLONG_X1 15 /* X1 pipe jump offset */ +#define R_TILEPRO_JOFFLONG_X1_PLT 16 /* X1 pipe jump offset to PLT */ +#define R_TILEPRO_IMM8_X0 17 /* X0 pipe 8-bit */ +#define R_TILEPRO_IMM8_Y0 18 /* Y0 pipe 8-bit */ +#define R_TILEPRO_IMM8_X1 19 /* X1 pipe 8-bit */ +#define R_TILEPRO_IMM8_Y1 20 /* Y1 pipe 8-bit */ +#define R_TILEPRO_MT_IMM15_X1 21 /* X1 pipe mtspr */ +#define R_TILEPRO_MF_IMM15_X1 22 /* X1 pipe mfspr */ +#define R_TILEPRO_IMM16_X0 23 /* X0 pipe 16-bit */ +#define R_TILEPRO_IMM16_X1 24 /* X1 pipe 16-bit */ +#define R_TILEPRO_IMM16_X0_LO 25 /* X0 pipe low 16-bit */ +#define R_TILEPRO_IMM16_X1_LO 26 /* X1 pipe low 16-bit */ +#define R_TILEPRO_IMM16_X0_HI 27 /* X0 pipe high 16-bit */ +#define R_TILEPRO_IMM16_X1_HI 28 /* X1 pipe high 16-bit */ +#define R_TILEPRO_IMM16_X0_HA 29 /* X0 pipe high 16-bit, adjusted */ +#define R_TILEPRO_IMM16_X1_HA 30 /* X1 pipe high 16-bit, adjusted */ +#define R_TILEPRO_IMM16_X0_PCREL 31 /* X0 pipe PC relative 16 bit */ +#define R_TILEPRO_IMM16_X1_PCREL 32 /* X1 pipe PC relative 16 bit */ +#define R_TILEPRO_IMM16_X0_LO_PCREL 33 /* X0 pipe PC relative low 16 bit */ +#define R_TILEPRO_IMM16_X1_LO_PCREL 34 /* X1 pipe PC relative low 16 bit */ +#define R_TILEPRO_IMM16_X0_HI_PCREL 35 /* X0 pipe PC relative high 16 bit */ +#define R_TILEPRO_IMM16_X1_HI_PCREL 36 /* X1 pipe PC relative high 16 bit */ +#define R_TILEPRO_IMM16_X0_HA_PCREL 37 /* X0 pipe PC relative ha() 16 bit */ +#define R_TILEPRO_IMM16_X1_HA_PCREL 38 /* X1 pipe PC relative ha() 16 bit */ +#define R_TILEPRO_IMM16_X0_GOT 39 /* X0 pipe 16-bit GOT offset */ +#define R_TILEPRO_IMM16_X1_GOT 40 /* X1 pipe 16-bit GOT offset */ +#define R_TILEPRO_IMM16_X0_GOT_LO 41 /* X0 pipe low 16-bit GOT offset */ +#define R_TILEPRO_IMM16_X1_GOT_LO 42 /* X1 pipe low 16-bit GOT offset */ +#define R_TILEPRO_IMM16_X0_GOT_HI 43 /* X0 pipe high 16-bit GOT offset */ +#define R_TILEPRO_IMM16_X1_GOT_HI 44 /* X1 pipe high 16-bit GOT offset */ +#define R_TILEPRO_IMM16_X0_GOT_HA 45 /* X0 pipe ha() 16-bit GOT offset */ +#define R_TILEPRO_IMM16_X1_GOT_HA 46 /* X1 pipe ha() 16-bit GOT offset */ +#define R_TILEPRO_MMSTART_X0 47 /* X0 pipe mm "start" */ +#define R_TILEPRO_MMEND_X0 48 /* X0 pipe mm "end" */ +#define R_TILEPRO_MMSTART_X1 49 /* X1 pipe mm "start" */ +#define R_TILEPRO_MMEND_X1 50 /* X1 pipe mm "end" */ +#define R_TILEPRO_SHAMT_X0 51 /* X0 pipe shift amount */ +#define R_TILEPRO_SHAMT_X1 52 /* X1 pipe shift amount */ +#define R_TILEPRO_SHAMT_Y0 53 /* Y0 pipe shift amount */ +#define R_TILEPRO_SHAMT_Y1 54 /* Y1 pipe shift amount */ +#define R_TILEPRO_DEST_IMM8_X1 55 /* X1 pipe destination 8-bit */ +/* Relocs 56-59 are currently not defined. */ +#define R_TILEPRO_TLS_GD_CALL 60 /* "jal" for TLS GD */ +#define R_TILEPRO_IMM8_X0_TLS_GD_ADD 61 /* X0 pipe "addi" for TLS GD */ +#define R_TILEPRO_IMM8_X1_TLS_GD_ADD 62 /* X1 pipe "addi" for TLS GD */ +#define R_TILEPRO_IMM8_Y0_TLS_GD_ADD 63 /* Y0 pipe "addi" for TLS GD */ +#define R_TILEPRO_IMM8_Y1_TLS_GD_ADD 64 /* Y1 pipe "addi" for TLS GD */ +#define R_TILEPRO_TLS_IE_LOAD 65 /* "lw_tls" for TLS IE */ +#define R_TILEPRO_IMM16_X0_TLS_GD 66 /* X0 pipe 16-bit TLS GD offset */ +#define R_TILEPRO_IMM16_X1_TLS_GD 67 /* X1 pipe 16-bit TLS GD offset */ +#define R_TILEPRO_IMM16_X0_TLS_GD_LO 68 /* X0 pipe low 16-bit TLS GD offset */ +#define R_TILEPRO_IMM16_X1_TLS_GD_LO 69 /* X1 pipe low 16-bit TLS GD offset */ +#define R_TILEPRO_IMM16_X0_TLS_GD_HI 70 /* X0 pipe high 16-bit TLS GD offset */ +#define R_TILEPRO_IMM16_X1_TLS_GD_HI 71 /* X1 pipe high 16-bit TLS GD offset */ +#define R_TILEPRO_IMM16_X0_TLS_GD_HA 72 /* X0 pipe ha() 16-bit TLS GD offset */ +#define R_TILEPRO_IMM16_X1_TLS_GD_HA 73 /* X1 pipe ha() 16-bit TLS GD offset */ +#define R_TILEPRO_IMM16_X0_TLS_IE 74 /* X0 pipe 16-bit TLS IE offset */ +#define R_TILEPRO_IMM16_X1_TLS_IE 75 /* X1 pipe 16-bit TLS IE offset */ +#define R_TILEPRO_IMM16_X0_TLS_IE_LO 76 /* X0 pipe low 16-bit TLS IE offset */ +#define R_TILEPRO_IMM16_X1_TLS_IE_LO 77 /* X1 pipe low 16-bit TLS IE offset */ +#define R_TILEPRO_IMM16_X0_TLS_IE_HI 78 /* X0 pipe high 16-bit TLS IE offset */ +#define R_TILEPRO_IMM16_X1_TLS_IE_HI 79 /* X1 pipe high 16-bit TLS IE offset */ +#define R_TILEPRO_IMM16_X0_TLS_IE_HA 80 /* X0 pipe ha() 16-bit TLS IE offset */ +#define R_TILEPRO_IMM16_X1_TLS_IE_HA 81 /* X1 pipe ha() 16-bit TLS IE offset */ +#define R_TILEPRO_TLS_DTPMOD32 82 /* ID of module containing symbol */ +#define R_TILEPRO_TLS_DTPOFF32 83 /* Offset in TLS block */ +#define R_TILEPRO_TLS_TPOFF32 84 /* Offset in static TLS block */ +#define R_TILEPRO_IMM16_X0_TLS_LE 85 /* X0 pipe 16-bit TLS LE offset */ +#define R_TILEPRO_IMM16_X1_TLS_LE 86 /* X1 pipe 16-bit TLS LE offset */ +#define R_TILEPRO_IMM16_X0_TLS_LE_LO 87 /* X0 pipe low 16-bit TLS LE offset */ +#define R_TILEPRO_IMM16_X1_TLS_LE_LO 88 /* X1 pipe low 16-bit TLS LE offset */ +#define R_TILEPRO_IMM16_X0_TLS_LE_HI 89 /* X0 pipe high 16-bit TLS LE offset */ +#define R_TILEPRO_IMM16_X1_TLS_LE_HI 90 /* X1 pipe high 16-bit TLS LE offset */ +#define R_TILEPRO_IMM16_X0_TLS_LE_HA 91 /* X0 pipe ha() 16-bit TLS LE offset */ +#define R_TILEPRO_IMM16_X1_TLS_LE_HA 92 /* X1 pipe ha() 16-bit TLS LE offset */ + +#define R_TILEPRO_GNU_VTINHERIT 128 /* GNU C++ vtable hierarchy */ +#define R_TILEPRO_GNU_VTENTRY 129 /* GNU C++ vtable member usage */ + +#define R_TILEPRO_NUM 130 + + +/* TILE-Gx relocations. */ +#define R_TILEGX_NONE 0 /* No reloc */ +#define R_TILEGX_64 1 /* Direct 64 bit */ +#define R_TILEGX_32 2 /* Direct 32 bit */ +#define R_TILEGX_16 3 /* Direct 16 bit */ +#define R_TILEGX_8 4 /* Direct 8 bit */ +#define R_TILEGX_64_PCREL 5 /* PC relative 64 bit */ +#define R_TILEGX_32_PCREL 6 /* PC relative 32 bit */ +#define R_TILEGX_16_PCREL 7 /* PC relative 16 bit */ +#define R_TILEGX_8_PCREL 8 /* PC relative 8 bit */ +#define R_TILEGX_HW0 9 /* hword 0 16-bit */ +#define R_TILEGX_HW1 10 /* hword 1 16-bit */ +#define R_TILEGX_HW2 11 /* hword 2 16-bit */ +#define R_TILEGX_HW3 12 /* hword 3 16-bit */ +#define R_TILEGX_HW0_LAST 13 /* last hword 0 16-bit */ +#define R_TILEGX_HW1_LAST 14 /* last hword 1 16-bit */ +#define R_TILEGX_HW2_LAST 15 /* last hword 2 16-bit */ +#define R_TILEGX_COPY 16 /* Copy relocation */ +#define R_TILEGX_GLOB_DAT 17 /* Create GOT entry */ +#define R_TILEGX_JMP_SLOT 18 /* Create PLT entry */ +#define R_TILEGX_RELATIVE 19 /* Adjust by program base */ +#define R_TILEGX_BROFF_X1 20 /* X1 pipe branch offset */ +#define R_TILEGX_JUMPOFF_X1 21 /* X1 pipe jump offset */ +#define R_TILEGX_JUMPOFF_X1_PLT 22 /* X1 pipe jump offset to PLT */ +#define R_TILEGX_IMM8_X0 23 /* X0 pipe 8-bit */ +#define R_TILEGX_IMM8_Y0 24 /* Y0 pipe 8-bit */ +#define R_TILEGX_IMM8_X1 25 /* X1 pipe 8-bit */ +#define R_TILEGX_IMM8_Y1 26 /* Y1 pipe 8-bit */ +#define R_TILEGX_DEST_IMM8_X1 27 /* X1 pipe destination 8-bit */ +#define R_TILEGX_MT_IMM14_X1 28 /* X1 pipe mtspr */ +#define R_TILEGX_MF_IMM14_X1 29 /* X1 pipe mfspr */ +#define R_TILEGX_MMSTART_X0 30 /* X0 pipe mm "start" */ +#define R_TILEGX_MMEND_X0 31 /* X0 pipe mm "end" */ +#define R_TILEGX_SHAMT_X0 32 /* X0 pipe shift amount */ +#define R_TILEGX_SHAMT_X1 33 /* X1 pipe shift amount */ +#define R_TILEGX_SHAMT_Y0 34 /* Y0 pipe shift amount */ +#define R_TILEGX_SHAMT_Y1 35 /* Y1 pipe shift amount */ +#define R_TILEGX_IMM16_X0_HW0 36 /* X0 pipe hword 0 */ +#define R_TILEGX_IMM16_X1_HW0 37 /* X1 pipe hword 0 */ +#define R_TILEGX_IMM16_X0_HW1 38 /* X0 pipe hword 1 */ +#define R_TILEGX_IMM16_X1_HW1 39 /* X1 pipe hword 1 */ +#define R_TILEGX_IMM16_X0_HW2 40 /* X0 pipe hword 2 */ +#define R_TILEGX_IMM16_X1_HW2 41 /* X1 pipe hword 2 */ +#define R_TILEGX_IMM16_X0_HW3 42 /* X0 pipe hword 3 */ +#define R_TILEGX_IMM16_X1_HW3 43 /* X1 pipe hword 3 */ +#define R_TILEGX_IMM16_X0_HW0_LAST 44 /* X0 pipe last hword 0 */ +#define R_TILEGX_IMM16_X1_HW0_LAST 45 /* X1 pipe last hword 0 */ +#define R_TILEGX_IMM16_X0_HW1_LAST 46 /* X0 pipe last hword 1 */ +#define R_TILEGX_IMM16_X1_HW1_LAST 47 /* X1 pipe last hword 1 */ +#define R_TILEGX_IMM16_X0_HW2_LAST 48 /* X0 pipe last hword 2 */ +#define R_TILEGX_IMM16_X1_HW2_LAST 49 /* X1 pipe last hword 2 */ +#define R_TILEGX_IMM16_X0_HW0_PCREL 50 /* X0 pipe PC relative hword 0 */ +#define R_TILEGX_IMM16_X1_HW0_PCREL 51 /* X1 pipe PC relative hword 0 */ +#define R_TILEGX_IMM16_X0_HW1_PCREL 52 /* X0 pipe PC relative hword 1 */ +#define R_TILEGX_IMM16_X1_HW1_PCREL 53 /* X1 pipe PC relative hword 1 */ +#define R_TILEGX_IMM16_X0_HW2_PCREL 54 /* X0 pipe PC relative hword 2 */ +#define R_TILEGX_IMM16_X1_HW2_PCREL 55 /* X1 pipe PC relative hword 2 */ +#define R_TILEGX_IMM16_X0_HW3_PCREL 56 /* X0 pipe PC relative hword 3 */ +#define R_TILEGX_IMM16_X1_HW3_PCREL 57 /* X1 pipe PC relative hword 3 */ +#define R_TILEGX_IMM16_X0_HW0_LAST_PCREL 58 /* X0 pipe PC-rel last hword 0 */ +#define R_TILEGX_IMM16_X1_HW0_LAST_PCREL 59 /* X1 pipe PC-rel last hword 0 */ +#define R_TILEGX_IMM16_X0_HW1_LAST_PCREL 60 /* X0 pipe PC-rel last hword 1 */ +#define R_TILEGX_IMM16_X1_HW1_LAST_PCREL 61 /* X1 pipe PC-rel last hword 1 */ +#define R_TILEGX_IMM16_X0_HW2_LAST_PCREL 62 /* X0 pipe PC-rel last hword 2 */ +#define R_TILEGX_IMM16_X1_HW2_LAST_PCREL 63 /* X1 pipe PC-rel last hword 2 */ +#define R_TILEGX_IMM16_X0_HW0_GOT 64 /* X0 pipe hword 0 GOT offset */ +#define R_TILEGX_IMM16_X1_HW0_GOT 65 /* X1 pipe hword 0 GOT offset */ +#define R_TILEGX_IMM16_X0_HW0_PLT_PCREL 66 /* X0 pipe PC-rel PLT hword 0 */ +#define R_TILEGX_IMM16_X1_HW0_PLT_PCREL 67 /* X1 pipe PC-rel PLT hword 0 */ +#define R_TILEGX_IMM16_X0_HW1_PLT_PCREL 68 /* X0 pipe PC-rel PLT hword 1 */ +#define R_TILEGX_IMM16_X1_HW1_PLT_PCREL 69 /* X1 pipe PC-rel PLT hword 1 */ +#define R_TILEGX_IMM16_X0_HW2_PLT_PCREL 70 /* X0 pipe PC-rel PLT hword 2 */ +#define R_TILEGX_IMM16_X1_HW2_PLT_PCREL 71 /* X1 pipe PC-rel PLT hword 2 */ +#define R_TILEGX_IMM16_X0_HW0_LAST_GOT 72 /* X0 pipe last hword 0 GOT offset */ +#define R_TILEGX_IMM16_X1_HW0_LAST_GOT 73 /* X1 pipe last hword 0 GOT offset */ +#define R_TILEGX_IMM16_X0_HW1_LAST_GOT 74 /* X0 pipe last hword 1 GOT offset */ +#define R_TILEGX_IMM16_X1_HW1_LAST_GOT 75 /* X1 pipe last hword 1 GOT offset */ +#define R_TILEGX_IMM16_X0_HW3_PLT_PCREL 76 /* X0 pipe PC-rel PLT hword 3 */ +#define R_TILEGX_IMM16_X1_HW3_PLT_PCREL 77 /* X1 pipe PC-rel PLT hword 3 */ +#define R_TILEGX_IMM16_X0_HW0_TLS_GD 78 /* X0 pipe hword 0 TLS GD offset */ +#define R_TILEGX_IMM16_X1_HW0_TLS_GD 79 /* X1 pipe hword 0 TLS GD offset */ +#define R_TILEGX_IMM16_X0_HW0_TLS_LE 80 /* X0 pipe hword 0 TLS LE offset */ +#define R_TILEGX_IMM16_X1_HW0_TLS_LE 81 /* X1 pipe hword 0 TLS LE offset */ +#define R_TILEGX_IMM16_X0_HW0_LAST_TLS_LE 82 /* X0 pipe last hword 0 LE off */ +#define R_TILEGX_IMM16_X1_HW0_LAST_TLS_LE 83 /* X1 pipe last hword 0 LE off */ +#define R_TILEGX_IMM16_X0_HW1_LAST_TLS_LE 84 /* X0 pipe last hword 1 LE off */ +#define R_TILEGX_IMM16_X1_HW1_LAST_TLS_LE 85 /* X1 pipe last hword 1 LE off */ +#define R_TILEGX_IMM16_X0_HW0_LAST_TLS_GD 86 /* X0 pipe last hword 0 GD off */ +#define R_TILEGX_IMM16_X1_HW0_LAST_TLS_GD 87 /* X1 pipe last hword 0 GD off */ +#define R_TILEGX_IMM16_X0_HW1_LAST_TLS_GD 88 /* X0 pipe last hword 1 GD off */ +#define R_TILEGX_IMM16_X1_HW1_LAST_TLS_GD 89 /* X1 pipe last hword 1 GD off */ +/* Relocs 90-91 are currently not defined. */ +#define R_TILEGX_IMM16_X0_HW0_TLS_IE 92 /* X0 pipe hword 0 TLS IE offset */ +#define R_TILEGX_IMM16_X1_HW0_TLS_IE 93 /* X1 pipe hword 0 TLS IE offset */ +#define R_TILEGX_IMM16_X0_HW0_LAST_PLT_PCREL 94 /* X0 pipe PC-rel PLT last hword 0 */ +#define R_TILEGX_IMM16_X1_HW0_LAST_PLT_PCREL 95 /* X1 pipe PC-rel PLT last hword 0 */ +#define R_TILEGX_IMM16_X0_HW1_LAST_PLT_PCREL 96 /* X0 pipe PC-rel PLT last hword 1 */ +#define R_TILEGX_IMM16_X1_HW1_LAST_PLT_PCREL 97 /* X1 pipe PC-rel PLT last hword 1 */ +#define R_TILEGX_IMM16_X0_HW2_LAST_PLT_PCREL 98 /* X0 pipe PC-rel PLT last hword 2 */ +#define R_TILEGX_IMM16_X1_HW2_LAST_PLT_PCREL 99 /* X1 pipe PC-rel PLT last hword 2 */ +#define R_TILEGX_IMM16_X0_HW0_LAST_TLS_IE 100 /* X0 pipe last hword 0 IE off */ +#define R_TILEGX_IMM16_X1_HW0_LAST_TLS_IE 101 /* X1 pipe last hword 0 IE off */ +#define R_TILEGX_IMM16_X0_HW1_LAST_TLS_IE 102 /* X0 pipe last hword 1 IE off */ +#define R_TILEGX_IMM16_X1_HW1_LAST_TLS_IE 103 /* X1 pipe last hword 1 IE off */ +/* Relocs 104-105 are currently not defined. */ +#define R_TILEGX_TLS_DTPMOD64 106 /* 64-bit ID of symbol's module */ +#define R_TILEGX_TLS_DTPOFF64 107 /* 64-bit offset in TLS block */ +#define R_TILEGX_TLS_TPOFF64 108 /* 64-bit offset in static TLS block */ +#define R_TILEGX_TLS_DTPMOD32 109 /* 32-bit ID of symbol's module */ +#define R_TILEGX_TLS_DTPOFF32 110 /* 32-bit offset in TLS block */ +#define R_TILEGX_TLS_TPOFF32 111 /* 32-bit offset in static TLS block */ +#define R_TILEGX_TLS_GD_CALL 112 /* "jal" for TLS GD */ +#define R_TILEGX_IMM8_X0_TLS_GD_ADD 113 /* X0 pipe "addi" for TLS GD */ +#define R_TILEGX_IMM8_X1_TLS_GD_ADD 114 /* X1 pipe "addi" for TLS GD */ +#define R_TILEGX_IMM8_Y0_TLS_GD_ADD 115 /* Y0 pipe "addi" for TLS GD */ +#define R_TILEGX_IMM8_Y1_TLS_GD_ADD 116 /* Y1 pipe "addi" for TLS GD */ +#define R_TILEGX_TLS_IE_LOAD 117 /* "ld_tls" for TLS IE */ +#define R_TILEGX_IMM8_X0_TLS_ADD 118 /* X0 pipe "addi" for TLS GD/IE */ +#define R_TILEGX_IMM8_X1_TLS_ADD 119 /* X1 pipe "addi" for TLS GD/IE */ +#define R_TILEGX_IMM8_Y0_TLS_ADD 120 /* Y0 pipe "addi" for TLS GD/IE */ +#define R_TILEGX_IMM8_Y1_TLS_ADD 121 /* Y1 pipe "addi" for TLS GD/IE */ + +#define R_TILEGX_GNU_VTINHERIT 128 /* GNU C++ vtable hierarchy */ +#define R_TILEGX_GNU_VTENTRY 129 /* GNU C++ vtable member usage */ + +#define R_TILEGX_NUM 130 + +/* RISC-V ELF Flags */ +#define EF_RISCV_RVC 0x0001 +#define EF_RISCV_FLOAT_ABI 0x0006 +#define EF_RISCV_FLOAT_ABI_SOFT 0x0000 +#define EF_RISCV_FLOAT_ABI_SINGLE 0x0002 +#define EF_RISCV_FLOAT_ABI_DOUBLE 0x0004 +#define EF_RISCV_FLOAT_ABI_QUAD 0x0006 + +/* RISC-V relocations. */ +#define R_RISCV_NONE 0 +#define R_RISCV_32 1 +#define R_RISCV_64 2 +#define R_RISCV_RELATIVE 3 +#define R_RISCV_COPY 4 +#define R_RISCV_JUMP_SLOT 5 +#define R_RISCV_TLS_DTPMOD32 6 +#define R_RISCV_TLS_DTPMOD64 7 +#define R_RISCV_TLS_DTPREL32 8 +#define R_RISCV_TLS_DTPREL64 9 +#define R_RISCV_TLS_TPREL32 10 +#define R_RISCV_TLS_TPREL64 11 +#define R_RISCV_BRANCH 16 +#define R_RISCV_JAL 17 +#define R_RISCV_CALL 18 +#define R_RISCV_CALL_PLT 19 +#define R_RISCV_GOT_HI20 20 +#define R_RISCV_TLS_GOT_HI20 21 +#define R_RISCV_TLS_GD_HI20 22 +#define R_RISCV_PCREL_HI20 23 +#define R_RISCV_PCREL_LO12_I 24 +#define R_RISCV_PCREL_LO12_S 25 +#define R_RISCV_HI20 26 +#define R_RISCV_LO12_I 27 +#define R_RISCV_LO12_S 28 +#define R_RISCV_TPREL_HI20 29 +#define R_RISCV_TPREL_LO12_I 30 +#define R_RISCV_TPREL_LO12_S 31 +#define R_RISCV_TPREL_ADD 32 +#define R_RISCV_ADD8 33 +#define R_RISCV_ADD16 34 +#define R_RISCV_ADD32 35 +#define R_RISCV_ADD64 36 +#define R_RISCV_SUB8 37 +#define R_RISCV_SUB16 38 +#define R_RISCV_SUB32 39 +#define R_RISCV_SUB64 40 +#define R_RISCV_GNU_VTINHERIT 41 +#define R_RISCV_GNU_VTENTRY 42 +#define R_RISCV_ALIGN 43 +#define R_RISCV_RVC_BRANCH 44 +#define R_RISCV_RVC_JUMP 45 +#define R_RISCV_RVC_LUI 46 +#define R_RISCV_GPREL_I 47 +#define R_RISCV_GPREL_S 48 +#define R_RISCV_TPREL_I 49 +#define R_RISCV_TPREL_S 50 +#define R_RISCV_RELAX 51 +#define R_RISCV_SUB6 52 +#define R_RISCV_SET6 53 +#define R_RISCV_SET8 54 +#define R_RISCV_SET16 55 +#define R_RISCV_SET32 56 +#define R_RISCV_32_PCREL 57 + +#define R_RISCV_NUM 58 + +/* BPF specific declarations. */ + +#define R_BPF_NONE 0 /* No reloc */ +#define R_BPF_64_64 1 +#define R_BPF_64_32 10 + +/* Imagination Meta specific relocations. */ + +#define R_METAG_HIADDR16 0 +#define R_METAG_LOADDR16 1 +#define R_METAG_ADDR32 2 /* 32bit absolute address */ +#define R_METAG_NONE 3 /* No reloc */ +#define R_METAG_RELBRANCH 4 +#define R_METAG_GETSETOFF 5 + +/* Backward compatability */ +#define R_METAG_REG32OP1 6 +#define R_METAG_REG32OP2 7 +#define R_METAG_REG32OP3 8 +#define R_METAG_REG16OP1 9 +#define R_METAG_REG16OP2 10 +#define R_METAG_REG16OP3 11 +#define R_METAG_REG32OP4 12 + +#define R_METAG_HIOG 13 +#define R_METAG_LOOG 14 + +#define R_METAG_REL8 15 +#define R_METAG_REL16 16 + +/* GNU */ +#define R_METAG_GNU_VTINHERIT 30 +#define R_METAG_GNU_VTENTRY 31 + +/* PIC relocations */ +#define R_METAG_HI16_GOTOFF 32 +#define R_METAG_LO16_GOTOFF 33 +#define R_METAG_GETSET_GOTOFF 34 +#define R_METAG_GETSET_GOT 35 +#define R_METAG_HI16_GOTPC 36 +#define R_METAG_LO16_GOTPC 37 +#define R_METAG_HI16_PLT 38 +#define R_METAG_LO16_PLT 39 +#define R_METAG_RELBRANCH_PLT 40 +#define R_METAG_GOTOFF 41 +#define R_METAG_PLT 42 +#define R_METAG_COPY 43 +#define R_METAG_JMP_SLOT 44 +#define R_METAG_RELATIVE 45 +#define R_METAG_GLOB_DAT 46 + +/* TLS relocations */ +#define R_METAG_TLS_GD 47 +#define R_METAG_TLS_LDM 48 +#define R_METAG_TLS_LDO_HI16 49 +#define R_METAG_TLS_LDO_LO16 50 +#define R_METAG_TLS_LDO 51 +#define R_METAG_TLS_IE 52 +#define R_METAG_TLS_IENONPIC 53 +#define R_METAG_TLS_IENONPIC_HI16 54 +#define R_METAG_TLS_IENONPIC_LO16 55 +#define R_METAG_TLS_TPOFF 56 +#define R_METAG_TLS_DTPMOD 57 +#define R_METAG_TLS_DTPOFF 58 +#define R_METAG_TLS_LE 59 +#define R_METAG_TLS_LE_HI16 60 +#define R_METAG_TLS_LE_LO16 61 + +/* NDS32 relocations. */ +#define R_NDS32_NONE 0 +#define R_NDS32_32_RELA 20 +#define R_NDS32_COPY 39 +#define R_NDS32_GLOB_DAT 40 +#define R_NDS32_JMP_SLOT 41 +#define R_NDS32_RELATIVE 42 +#define R_NDS32_TLS_TPOFF 102 +#define R_NDS32_TLS_DESC 119 + +#ifdef __cplusplus +} +#endif + +#endif /* elf.h */ diff --git a/third_party/gtest/BUILD.gn b/third_party/gtest/BUILD.gn index db5f4a56..4764604c 100644 --- a/third_party/gtest/BUILD.gn +++ b/third_party/gtest/BUILD.gn @@ -28,12 +28,13 @@ if (crashpad_is_in_chromium) { "//testing/gmock", ] } -} else if (crashpad_is_in_fuchsia) { +} else if (crashpad_is_in_dart || crashpad_is_in_fuchsia) { group("gtest") { testonly = true public_deps = [ "//third_party/googletest:gtest", ] + public_configs = [ "../..:disable_ubsan" ] } group("gmock") { testonly = true @@ -56,6 +57,7 @@ if (crashpad_is_in_chromium) { testonly = true sources = [ "gtest/googletest/include/gtest/gtest-death-test.h", + "gtest/googletest/include/gtest/gtest-matchers.h", "gtest/googletest/include/gtest/gtest-message.h", "gtest/googletest/include/gtest/gtest-param-test.h", "gtest/googletest/include/gtest/gtest-printers.h", @@ -71,18 +73,16 @@ if (crashpad_is_in_chromium) { "gtest/googletest/include/gtest/internal/gtest-death-test-internal.h", "gtest/googletest/include/gtest/internal/gtest-filepath.h", "gtest/googletest/include/gtest/internal/gtest-internal.h", - "gtest/googletest/include/gtest/internal/gtest-linked_ptr.h", - "gtest/googletest/include/gtest/internal/gtest-param-util-generated.h", "gtest/googletest/include/gtest/internal/gtest-param-util.h", "gtest/googletest/include/gtest/internal/gtest-port-arch.h", "gtest/googletest/include/gtest/internal/gtest-port.h", "gtest/googletest/include/gtest/internal/gtest-string.h", - "gtest/googletest/include/gtest/internal/gtest-tuple.h", "gtest/googletest/include/gtest/internal/gtest-type-util.h", "gtest/googletest/src/gtest-all.cc", "gtest/googletest/src/gtest-death-test.cc", "gtest/googletest/src/gtest-filepath.cc", "gtest/googletest/src/gtest-internal-inl.h", + "gtest/googletest/src/gtest-matchers.cc", "gtest/googletest/src/gtest-port.cc", "gtest/googletest/src/gtest-printers.cc", "gtest/googletest/src/gtest-test-part.cc", @@ -95,6 +95,11 @@ if (crashpad_is_in_chromium) { "//third_party/mini_chromium/mini_chromium/build:Wexit_time_destructors", ] configs += [ ":gtest_private_config" ] + if (crashpad_is_fuchsia) { + deps = [ + "../fuchsia", + ] + } } static_library("gtest_main") { @@ -112,20 +117,20 @@ if (crashpad_is_in_chromium) { test("gtest_all_test") { sources = [ - "gtest/googletest/test/gtest-death-test_test.cc", - "gtest/googletest/test/gtest-filepath_test.cc", - "gtest/googletest/test/gtest-linked_ptr_test.cc", - "gtest/googletest/test/gtest-message_test.cc", - "gtest/googletest/test/gtest-options_test.cc", - "gtest/googletest/test/gtest-port_test.cc", - "gtest/googletest/test/gtest-printers_test.cc", - "gtest/googletest/test/gtest-test-part_test.cc", + "gtest/googletest/test/googletest-death-test-test.cc", + "gtest/googletest/test/googletest-filepath-test.cc", + "gtest/googletest/test/googletest-message-test.cc", + "gtest/googletest/test/googletest-options-test.cc", + "gtest/googletest/test/googletest-port-test.cc", + "gtest/googletest/test/googletest-printers-test.cc", + "gtest/googletest/test/googletest-test-part-test.cc", "gtest/googletest/test/gtest-typed-test2_test.cc", "gtest/googletest/test/gtest-typed-test_test.cc", "gtest/googletest/test/gtest-typed-test_test.h", "gtest/googletest/test/gtest_main_unittest.cc", "gtest/googletest/test/gtest_pred_impl_unittest.cc", "gtest/googletest/test/gtest_prod_test.cc", + "gtest/googletest/test/gtest_skip_test.cc", "gtest/googletest/test/gtest_unittest.cc", "gtest/googletest/test/production.cc", "gtest/googletest/test/production.h", @@ -156,7 +161,7 @@ if (crashpad_is_in_chromium) { test("gtest_listener_test") { sources = [ - "gtest/googletest/test/gtest-listener_test.cc", + "gtest/googletest/test/googletest-listener-test.cc", ] deps = [ ":gtest", @@ -174,9 +179,9 @@ if (crashpad_is_in_chromium) { test("gtest_param_test") { sources = [ - "gtest/googletest/test/gtest-param-test2_test.cc", - "gtest/googletest/test/gtest-param-test_test.cc", - "gtest/googletest/test/gtest-param-test_test.h", + "gtest/googletest/test/googletest-param-test-test.cc", + "gtest/googletest/test/googletest-param-test-test.h", + "gtest/googletest/test/googletest-param-test2-test.cc", ] configs -= [ "//third_party/mini_chromium/mini_chromium/build:Wexit_time_destructors", @@ -284,21 +289,20 @@ if (crashpad_is_in_chromium) { sources = [ "gtest/googlemock/include/gmock/gmock-actions.h", "gtest/googlemock/include/gmock/gmock-cardinalities.h", + "gtest/googlemock/include/gmock/gmock-function-mocker.h", "gtest/googlemock/include/gmock/gmock-generated-actions.h", - "gtest/googlemock/include/gmock/gmock-generated-function-mockers.h", - "gtest/googlemock/include/gmock/gmock-generated-matchers.h", - "gtest/googlemock/include/gmock/gmock-generated-nice-strict.h", "gtest/googlemock/include/gmock/gmock-matchers.h", "gtest/googlemock/include/gmock/gmock-more-actions.h", "gtest/googlemock/include/gmock/gmock-more-matchers.h", + "gtest/googlemock/include/gmock/gmock-nice-strict.h", "gtest/googlemock/include/gmock/gmock-spec-builders.h", "gtest/googlemock/include/gmock/gmock.h", "gtest/googlemock/include/gmock/internal/custom/gmock-generated-actions.h", "gtest/googlemock/include/gmock/internal/custom/gmock-matchers.h", "gtest/googlemock/include/gmock/internal/custom/gmock-port.h", - "gtest/googlemock/include/gmock/internal/gmock-generated-internal-utils.h", "gtest/googlemock/include/gmock/internal/gmock-internal-utils.h", "gtest/googlemock/include/gmock/internal/gmock-port.h", + "gtest/googlemock/include/gmock/internal/gmock-pp.h", "gtest/googlemock/src/gmock-all.cc", "gtest/googlemock/src/gmock-cardinalities.cc", "gtest/googlemock/src/gmock-internal-utils.cc", @@ -334,15 +338,16 @@ if (crashpad_is_in_chromium) { sources = [ "gtest/googlemock/test/gmock-actions_test.cc", "gtest/googlemock/test/gmock-cardinalities_test.cc", + "gtest/googlemock/test/gmock-function-mocker_test.cc", "gtest/googlemock/test/gmock-generated-actions_test.cc", - "gtest/googlemock/test/gmock-generated-function-mockers_test.cc", - "gtest/googlemock/test/gmock-generated-internal-utils_test.cc", "gtest/googlemock/test/gmock-generated-matchers_test.cc", "gtest/googlemock/test/gmock-internal-utils_test.cc", "gtest/googlemock/test/gmock-matchers_test.cc", "gtest/googlemock/test/gmock-more-actions_test.cc", "gtest/googlemock/test/gmock-nice-strict_test.cc", "gtest/googlemock/test/gmock-port_test.cc", + "gtest/googlemock/test/gmock-pp-string_test.cc", + "gtest/googlemock/test/gmock-pp_test.cc", "gtest/googlemock/test/gmock-spec-builders_test.cc", "gtest/googlemock/test/gmock_test.cc", ] diff --git a/third_party/gtest/gmock.gyp b/third_party/gtest/gmock.gyp index d53c925f..10c0879a 100644 --- a/third_party/gtest/gmock.gyp +++ b/third_party/gtest/gmock.gyp @@ -56,18 +56,18 @@ 'sources': [ '<(gmock_dir)/include/gmock/gmock-actions.h', '<(gmock_dir)/include/gmock/gmock-cardinalities.h', + '<(gmock_dir)/include/gmock/gmock-function-mocker.h', '<(gmock_dir)/include/gmock/gmock-generated-actions.h', - '<(gmock_dir)/include/gmock/gmock-generated-function-mockers.h', - '<(gmock_dir)/include/gmock/gmock-generated-matchers.h', - '<(gmock_dir)/include/gmock/gmock-generated-nice-strict.h', '<(gmock_dir)/include/gmock/gmock-matchers.h', '<(gmock_dir)/include/gmock/gmock-more-actions.h', '<(gmock_dir)/include/gmock/gmock-more-matchers.h', + '<(gmock_dir)/include/gmock/gmock-nice-strict.h', '<(gmock_dir)/include/gmock/gmock-spec-builders.h', '<(gmock_dir)/include/gmock/gmock.h', '<(gmock_dir)/include/gmock/internal/custom/gmock-generated-actions.h', '<(gmock_dir)/include/gmock/internal/custom/gmock-matchers.h', '<(gmock_dir)/include/gmock/internal/custom/gmock-port.h', + '<(gmock_dir)/include/gmock/internal/custom/gmock-pp.h', '<(gmock_dir)/include/gmock/internal/gmock-generated-internal-utils.h', '<(gmock_dir)/include/gmock/internal/gmock-internal-utils.h', '<(gmock_dir)/include/gmock/internal/gmock-port.h', @@ -156,15 +156,16 @@ 'sources': [ '<(gmock_dir)/test/gmock-actions_test.cc', '<(gmock_dir)/test/gmock-cardinalities_test.cc', + '<(gmock_dir)/test/gmock-function-mocker_test.cc', '<(gmock_dir)/test/gmock-generated-actions_test.cc', - '<(gmock_dir)/test/gmock-generated-function-mockers_test.cc', - '<(gmock_dir)/test/gmock-generated-internal-utils_test.cc', '<(gmock_dir)/test/gmock-generated-matchers_test.cc', '<(gmock_dir)/test/gmock-internal-utils_test.cc', '<(gmock_dir)/test/gmock-matchers_test.cc', '<(gmock_dir)/test/gmock-more-actions_test.cc', '<(gmock_dir)/test/gmock-nice-strict_test.cc', '<(gmock_dir)/test/gmock-port_test.cc', + '<(gmock_dir)/test/gmock-pp-string_test.cc', + '<(gmock_dir)/test/gmock-pp_test.cc', '<(gmock_dir)/test/gmock-spec-builders_test.cc', '<(gmock_dir)/test/gmock_test.cc', ], diff --git a/third_party/gtest/gtest.gyp b/third_party/gtest/gtest.gyp index 5d93feb5..aa6399bc 100644 --- a/third_party/gtest/gtest.gyp +++ b/third_party/gtest/gtest.gyp @@ -67,6 +67,7 @@ ], 'sources': [ '<(gtest_dir)/include/gtest/gtest-death-test.h', + '<(gtest_dir)/include/gtest/gtest-matchers.h', '<(gtest_dir)/include/gtest/gtest-message.h', '<(gtest_dir)/include/gtest/gtest-param-test.h', '<(gtest_dir)/include/gtest/gtest-printers.h', @@ -82,18 +83,17 @@ '<(gtest_dir)/include/gtest/internal/gtest-death-test-internal.h', '<(gtest_dir)/include/gtest/internal/gtest-filepath.h', '<(gtest_dir)/include/gtest/internal/gtest-internal.h', - '<(gtest_dir)/include/gtest/internal/gtest-linked_ptr.h', '<(gtest_dir)/include/gtest/internal/gtest-param-util-generated.h', '<(gtest_dir)/include/gtest/internal/gtest-param-util.h', '<(gtest_dir)/include/gtest/internal/gtest-port-arch.h', '<(gtest_dir)/include/gtest/internal/gtest-port.h', '<(gtest_dir)/include/gtest/internal/gtest-string.h', - '<(gtest_dir)/include/gtest/internal/gtest-tuple.h', '<(gtest_dir)/include/gtest/internal/gtest-type-util.h', '<(gtest_dir)/src/gtest-all.cc', '<(gtest_dir)/src/gtest-death-test.cc', '<(gtest_dir)/src/gtest-filepath.cc', '<(gtest_dir)/src/gtest-internal-inl.h', + '<(gtest_dir)/src/gtest-matchers.cc', '<(gtest_dir)/src/gtest-port.cc', '<(gtest_dir)/src/gtest-printers.cc', '<(gtest_dir)/src/gtest-test-part.cc', @@ -174,6 +174,7 @@ '<(gtest_dir)/test/gtest_main_unittest.cc', '<(gtest_dir)/test/gtest_pred_impl_unittest.cc', '<(gtest_dir)/test/gtest_prod_test.cc', + '<(gtest_dir)/test/gtest_skip_test.cc', '<(gtest_dir)/test/gtest_unittest.cc', '<(gtest_dir)/test/production.cc', '<(gtest_dir)/test/production.h', diff --git a/third_party/lss/BUILD.gn b/third_party/lss/BUILD.gn new file mode 100644 index 00000000..c0652cdf --- /dev/null +++ b/third_party/lss/BUILD.gn @@ -0,0 +1,29 @@ +# Copyright 2019 The Crashpad Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("../../build/crashpad_buildconfig.gni") + +config("lss_config") { + if (crashpad_is_in_chromium) { + defines = [ "CRASHPAD_LSS_SOURCE_EXTERNAL" ] + } else { + defines = [ "CRASHPAD_LSS_SOURCE_EMBEDDED" ] + } +} + +source_set("lss") { + public_configs = [ ":lss_config" ] + + sources = [ "lss.h" ] +} diff --git a/third_party/lss/README.crashpad b/third_party/lss/README.crashpad new file mode 100644 index 00000000..c036ae0f --- /dev/null +++ b/third_party/lss/README.crashpad @@ -0,0 +1,16 @@ +Name: linux-syscall-support +Short Name: lss +URL: https://chromium.googlesource.com/linux-syscall-support/ +Revision: See DEPS +License: BSD 3-clause +License File: lss/linux-syscall-support.h +Security Critical: yes + +Description: +Every so often, projects need to directly embed Linux system calls instead of +calling the implementations in the system runtime library. This project +provides a header file that can be included into your application whenever you +need to make direct system calls. + +Local Modifications: +None. diff --git a/third_party/lss/lss.gyp b/third_party/lss/lss.gyp new file mode 100644 index 00000000..63b9b3ae --- /dev/null +++ b/third_party/lss/lss.gyp @@ -0,0 +1,27 @@ +# Copyright 2019 The Crashpad Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +{ + 'targets': [ + { + 'target_name': 'lss', + 'type': 'none', + 'sources': [ 'lss.h' ], + 'direct_dependent_settings': { + # gyp is only used in circumstances when the embedded lss is used. + 'defines': [ 'CRASHPAD_LSS_SOURCE_EMBEDDED' ] + }, + } + ] +} diff --git a/third_party/lss/lss.h b/third_party/lss/lss.h new file mode 100644 index 00000000..11209ff8 --- /dev/null +++ b/third_party/lss/lss.h @@ -0,0 +1,26 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_THIRD_PARTY_LSS_LSS_H_ +#define CRASHPAD_THIRD_PARTY_LSS_LSS_H_ + +#if defined(CRASHPAD_LSS_SOURCE_EXTERNAL) +#include "third_party/lss/linux_syscall_support.h" +#elif defined(CRASHPAD_LSS_SOURCE_EMBEDDED) +#include "third_party/lss/lss/linux_syscall_support.h" +#else +#error Unknown lss source +#endif + +#endif // CRASHPAD_THIRD_PARTY_LSS_LSS_H_ diff --git a/third_party/mini_chromium/BUILD.gn b/third_party/mini_chromium/BUILD.gn index e11d0111..50d15133 100644 --- a/third_party/mini_chromium/BUILD.gn +++ b/third_party/mini_chromium/BUILD.gn @@ -16,13 +16,11 @@ import("../../build/crashpad_buildconfig.gni") group("base") { if (crashpad_is_in_chromium) { - public_deps = [ - "//base", - ] + public_deps = [ "//base" ] } else if (crashpad_is_standalone || crashpad_is_in_fuchsia) { - public_deps = [ - "mini_chromium/base", - ] + public_deps = [ "mini_chromium/base" ] + } else if (crashpad_is_in_dart) { + public_deps = [ "//third_party/mini_chromium/mini_chromium/base" ] } } @@ -30,8 +28,6 @@ group("base_test_support") { testonly = true if (crashpad_is_in_chromium) { - public_deps = [ - "//base/test:test_support", - ] + public_deps = [ "//base/test:test_support" ] } } diff --git a/third_party/xnu/APPLE_LICENSE b/third_party/xnu/APPLE_LICENSE new file mode 100644 index 00000000..fe81a60c --- /dev/null +++ b/third_party/xnu/APPLE_LICENSE @@ -0,0 +1,367 @@ +APPLE PUBLIC SOURCE LICENSE +Version 2.0 - August 6, 2003 + +Please read this License carefully before downloading this software. +By downloading or using this software, you are agreeing to be bound by +the terms of this License. If you do not or cannot agree to the terms +of this License, please do not download or use the software. + +1. General; Definitions. This License applies to any program or other +work which Apple Computer, Inc. ("Apple") makes publicly available and +which contains a notice placed by Apple identifying such program or +work as "Original Code" and stating that it is subject to the terms of +this Apple Public Source License version 2.0 ("License"). As used in +this License: + +1.1 "Applicable Patent Rights" mean: (a) in the case where Apple is +the grantor of rights, (i) claims of patents that are now or hereafter +acquired, owned by or assigned to Apple and (ii) that cover subject +matter contained in the Original Code, but only to the extent +necessary to use, reproduce and/or distribute the Original Code +without infringement; and (b) in the case where You are the grantor of +rights, (i) claims of patents that are now or hereafter acquired, +owned by or assigned to You and (ii) that cover subject matter in Your +Modifications, taken alone or in combination with Original Code. + +1.2 "Contributor" means any person or entity that creates or +contributes to the creation of Modifications. + +1.3 "Covered Code" means the Original Code, Modifications, the +combination of Original Code and any Modifications, and/or any +respective portions thereof. + +1.4 "Externally Deploy" means: (a) to sublicense, distribute or +otherwise make Covered Code available, directly or indirectly, to +anyone other than You; and/or (b) to use Covered Code, alone or as +part of a Larger Work, in any way to provide a service, including but +not limited to delivery of content, through electronic communication +with a client other than You. + +1.5 "Larger Work" means a work which combines Covered Code or portions +thereof with code not governed by the terms of this License. + +1.6 "Modifications" mean any addition to, deletion from, and/or change +to, the substance and/or structure of the Original Code, any previous +Modifications, the combination of Original Code and any previous +Modifications, and/or any respective portions thereof. When code is +released as a series of files, a Modification is: (a) any addition to +or deletion from the contents of a file containing Covered Code; +and/or (b) any new file or other representation of computer program +statements that contains any part of Covered Code. + +1.7 "Original Code" means (a) the Source Code of a program or other +work as originally made available by Apple under this License, +including the Source Code of any updates or upgrades to such programs +or works made available by Apple under this License, and that has been +expressly identified by Apple as such in the header file(s) of such +work; and (b) the object code compiled from such Source Code and +originally made available by Apple under this License. + +1.8 "Source Code" means the human readable form of a program or other +work that is suitable for making modifications to it, including all +modules it contains, plus any associated interface definition files, +scripts used to control compilation and installation of an executable +(object code). + +1.9 "You" or "Your" means an individual or a legal entity exercising +rights under this License. For legal entities, "You" or "Your" +includes any entity which controls, is controlled by, or is under +common control with, You, where "control" means (a) the power, direct +or indirect, to cause the direction or management of such entity, +whether by contract or otherwise, or (b) ownership of fifty percent +(50%) or more of the outstanding shares or beneficial ownership of +such entity. + +2. Permitted Uses; Conditions & Restrictions. Subject to the terms +and conditions of this License, Apple hereby grants You, effective on +the date You accept this License and download the Original Code, a +world-wide, royalty-free, non-exclusive license, to the extent of +Apple's Applicable Patent Rights and copyrights covering the Original +Code, to do the following: + +2.1 Unmodified Code. You may use, reproduce, display, perform, +internally distribute within Your organization, and Externally Deploy +verbatim, unmodified copies of the Original Code, for commercial or +non-commercial purposes, provided that in each instance: + +(a) You must retain and reproduce in all copies of Original Code the +copyright and other proprietary notices and disclaimers of Apple as +they appear in the Original Code, and keep intact all notices in the +Original Code that refer to this License; and + +(b) You must include a copy of this License with every copy of Source +Code of Covered Code and documentation You distribute or Externally +Deploy, and You may not offer or impose any terms on such Source Code +that alter or restrict this License or the recipients' rights +hereunder, except as permitted under Section 6. + +2.2 Modified Code. You may modify Covered Code and use, reproduce, +display, perform, internally distribute within Your organization, and +Externally Deploy Your Modifications and Covered Code, for commercial +or non-commercial purposes, provided that in each instance You also +meet all of these conditions: + +(a) You must satisfy all the conditions of Section 2.1 with respect to +the Source Code of the Covered Code; + +(b) You must duplicate, to the extent it does not already exist, the +notice in Exhibit A in each file of the Source Code of all Your +Modifications, and cause the modified files to carry prominent notices +stating that You changed the files and the date of any change; and + +(c) If You Externally Deploy Your Modifications, You must make +Source Code of all Your Externally Deployed Modifications either +available to those to whom You have Externally Deployed Your +Modifications, or publicly available. Source Code of Your Externally +Deployed Modifications must be released under the terms set forth in +this License, including the license grants set forth in Section 3 +below, for as long as you Externally Deploy the Covered Code or twelve +(12) months from the date of initial External Deployment, whichever is +longer. You should preferably distribute the Source Code of Your +Externally Deployed Modifications electronically (e.g. download from a +web site). + +2.3 Distribution of Executable Versions. In addition, if You +Externally Deploy Covered Code (Original Code and/or Modifications) in +object code, executable form only, You must include a prominent +notice, in the code itself as well as in related documentation, +stating that Source Code of the Covered Code is available under the +terms of this License with information on how and where to obtain such +Source Code. + +2.4 Third Party Rights. You expressly acknowledge and agree that +although Apple and each Contributor grants the licenses to their +respective portions of the Covered Code set forth herein, no +assurances are provided by Apple or any Contributor that the Covered +Code does not infringe the patent or other intellectual property +rights of any other entity. Apple and each Contributor disclaim any +liability to You for claims brought by any other entity based on +infringement of intellectual property rights or otherwise. As a +condition to exercising the rights and licenses granted hereunder, You +hereby assume sole responsibility to secure any other intellectual +property rights needed, if any. For example, if a third party patent +license is required to allow You to distribute the Covered Code, it is +Your responsibility to acquire that license before distributing the +Covered Code. + +3. Your Grants. In consideration of, and as a condition to, the +licenses granted to You under this License, You hereby grant to any +person or entity receiving or distributing Covered Code under this +License a non-exclusive, royalty-free, perpetual, irrevocable license, +under Your Applicable Patent Rights and other intellectual property +rights (other than patent) owned or controlled by You, to use, +reproduce, display, perform, modify, sublicense, distribute and +Externally Deploy Your Modifications of the same scope and extent as +Apple's licenses under Sections 2.1 and 2.2 above. + +4. Larger Works. You may create a Larger Work by combining Covered +Code with other code not governed by the terms of this License and +distribute the Larger Work as a single product. In each such instance, +You must make sure the requirements of this License are fulfilled for +the Covered Code or any portion thereof. + +5. Limitations on Patent License. Except as expressly stated in +Section 2, no other patent rights, express or implied, are granted by +Apple herein. Modifications and/or Larger Works may require additional +patent licenses from Apple which Apple may grant in its sole +discretion. + +6. Additional Terms. You may choose to offer, and to charge a fee for, +warranty, support, indemnity or liability obligations and/or other +rights consistent with the scope of the license granted herein +("Additional Terms") to one or more recipients of Covered Code. +However, You may do so only on Your own behalf and as Your sole +responsibility, and not on behalf of Apple or any Contributor. You +must obtain the recipient's agreement that any such Additional Terms +are offered by You alone, and You hereby agree to indemnify, defend +and hold Apple and every Contributor harmless for any liability +incurred by or claims asserted against Apple or such Contributor by +reason of any such Additional Terms. + +7. Versions of the License. Apple may publish revised and/or new +versions of this License from time to time. Each version will be given +a distinguishing version number. Once Original Code has been published +under a particular version of this License, You may continue to use it +under the terms of that version. You may also choose to use such +Original Code under the terms of any subsequent version of this +License published by Apple. No one other than Apple has the right to +modify the terms applicable to Covered Code created under this +License. + +8. NO WARRANTY OR SUPPORT. The Covered Code may contain in whole or in +part pre-release, untested, or not fully tested works. The Covered +Code may contain errors that could cause failures or loss of data, and +may be incomplete or contain inaccuracies. You expressly acknowledge +and agree that use of the Covered Code, or any portion thereof, is at +Your sole and entire risk. THE COVERED CODE IS PROVIDED "AS IS" AND +WITHOUT WARRANTY, UPGRADES OR SUPPORT OF ANY KIND AND APPLE AND +APPLE'S LICENSOR(S) (COLLECTIVELY REFERRED TO AS "APPLE" FOR THE +PURPOSES OF SECTIONS 8 AND 9) AND ALL CONTRIBUTORS EXPRESSLY DISCLAIM +ALL WARRANTIES AND/OR CONDITIONS, EXPRESS OR IMPLIED, INCLUDING, BUT +NOT LIMITED TO, THE IMPLIED WARRANTIES AND/OR CONDITIONS OF +MERCHANTABILITY, OF SATISFACTORY QUALITY, OF FITNESS FOR A PARTICULAR +PURPOSE, OF ACCURACY, OF QUIET ENJOYMENT, AND NONINFRINGEMENT OF THIRD +PARTY RIGHTS. APPLE AND EACH CONTRIBUTOR DOES NOT WARRANT AGAINST +INTERFERENCE WITH YOUR ENJOYMENT OF THE COVERED CODE, THAT THE +FUNCTIONS CONTAINED IN THE COVERED CODE WILL MEET YOUR REQUIREMENTS, +THAT THE OPERATION OF THE COVERED CODE WILL BE UNINTERRUPTED OR +ERROR-FREE, OR THAT DEFECTS IN THE COVERED CODE WILL BE CORRECTED. NO +ORAL OR WRITTEN INFORMATION OR ADVICE GIVEN BY APPLE, AN APPLE +AUTHORIZED REPRESENTATIVE OR ANY CONTRIBUTOR SHALL CREATE A WARRANTY. +You acknowledge that the Covered Code is not intended for use in the +operation of nuclear facilities, aircraft navigation, communication +systems, or air traffic control machines in which case the failure of +the Covered Code could lead to death, personal injury, or severe +physical or environmental damage. + +9. LIMITATION OF LIABILITY. TO THE EXTENT NOT PROHIBITED BY LAW, IN NO +EVENT SHALL APPLE OR ANY CONTRIBUTOR BE LIABLE FOR ANY INCIDENTAL, +SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR RELATING +TO THIS LICENSE OR YOUR USE OR INABILITY TO USE THE COVERED CODE, OR +ANY PORTION THEREOF, WHETHER UNDER A THEORY OF CONTRACT, WARRANTY, +TORT (INCLUDING NEGLIGENCE), PRODUCTS LIABILITY OR OTHERWISE, EVEN IF +APPLE OR SUCH CONTRIBUTOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES AND NOTWITHSTANDING THE FAILURE OF ESSENTIAL PURPOSE OF ANY +REMEDY. SOME JURISDICTIONS DO NOT ALLOW THE LIMITATION OF LIABILITY OF +INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THIS LIMITATION MAY NOT APPLY +TO YOU. In no event shall Apple's total liability to You for all +damages (other than as may be required by applicable law) under this +License exceed the amount of fifty dollars ($50.00). + +10. Trademarks. This License does not grant any rights to use the +trademarks or trade names "Apple", "Apple Computer", "Mac", "Mac OS", +"QuickTime", "QuickTime Streaming Server" or any other trademarks, +service marks, logos or trade names belonging to Apple (collectively +"Apple Marks") or to any trademark, service mark, logo or trade name +belonging to any Contributor. You agree not to use any Apple Marks in +or as part of the name of products derived from the Original Code or +to endorse or promote products derived from the Original Code other +than as expressly permitted by and in strict compliance at all times +with Apple's third party trademark usage guidelines which are posted +at http://www.apple.com/legal/guidelinesfor3rdparties.html. + +11. Ownership. Subject to the licenses granted under this License, +each Contributor retains all rights, title and interest in and to any +Modifications made by such Contributor. Apple retains all rights, +title and interest in and to the Original Code and any Modifications +made by or on behalf of Apple ("Apple Modifications"), and such Apple +Modifications will not be automatically subject to this License. Apple +may, at its sole discretion, choose to license such Apple +Modifications under this License, or on different terms from those +contained in this License or may choose not to license them at all. + +12. Termination. + +12.1 Termination. This License and the rights granted hereunder will +terminate: + +(a) automatically without notice from Apple if You fail to comply with +any term(s) of this License and fail to cure such breach within 30 +days of becoming aware of such breach; + +(b) immediately in the event of the circumstances described in Section +13.5(b); or + +(c) automatically without notice from Apple if You, at any time during +the term of this License, commence an action for patent infringement +against Apple; provided that Apple did not first commence +an action for patent infringement against You in that instance. + +12.2 Effect of Termination. Upon termination, You agree to immediately +stop any further use, reproduction, modification, sublicensing and +distribution of the Covered Code. All sublicenses to the Covered Code +which have been properly granted prior to termination shall survive +any termination of this License. Provisions which, by their nature, +should remain in effect beyond the termination of this License shall +survive, including but not limited to Sections 3, 5, 8, 9, 10, 11, +12.2 and 13. No party will be liable to any other for compensation, +indemnity or damages of any sort solely as a result of terminating +this License in accordance with its terms, and termination of this +License will be without prejudice to any other right or remedy of +any party. + +13. Miscellaneous. + +13.1 Government End Users. The Covered Code is a "commercial item" as +defined in FAR 2.101. Government software and technical data rights in +the Covered Code include only those rights customarily provided to the +public as defined in this License. This customary commercial license +in technical data and software is provided in accordance with FAR +12.211 (Technical Data) and 12.212 (Computer Software) and, for +Department of Defense purchases, DFAR 252.227-7015 (Technical Data -- +Commercial Items) and 227.7202-3 (Rights in Commercial Computer +Software or Computer Software Documentation). Accordingly, all U.S. +Government End Users acquire Covered Code with only those rights set +forth herein. + +13.2 Relationship of Parties. This License will not be construed as +creating an agency, partnership, joint venture or any other form of +legal association between or among You, Apple or any Contributor, and +You will not represent to the contrary, whether expressly, by +implication, appearance or otherwise. + +13.3 Independent Development. Nothing in this License will impair +Apple's right to acquire, license, develop, have others develop for +it, market and/or distribute technology or products that perform the +same or similar functions as, or otherwise compete with, +Modifications, Larger Works, technology or products that You may +develop, produce, market or distribute. + +13.4 Waiver; Construction. Failure by Apple or any Contributor to +enforce any provision of this License will not be deemed a waiver of +future enforcement of that or any other provision. Any law or +regulation which provides that the language of a contract shall be +construed against the drafter will not apply to this License. + +13.5 Severability. (a) If for any reason a court of competent +jurisdiction finds any provision of this License, or portion thereof, +to be unenforceable, that provision of the License will be enforced to +the maximum extent permissible so as to effect the economic benefits +and intent of the parties, and the remainder of this License will +continue in full force and effect. (b) Notwithstanding the foregoing, +if applicable law prohibits or restricts You from fully and/or +specifically complying with Sections 2 and/or 3 or prevents the +enforceability of either of those Sections, this License will +immediately terminate and You must immediately discontinue any use of +the Covered Code and destroy all copies of it that are in your +possession or control. + +13.6 Dispute Resolution. Any litigation or other dispute resolution +between You and Apple relating to this License shall take place in the +Northern District of California, and You and Apple hereby consent to +the personal jurisdiction of, and venue in, the state and federal +courts within that District with respect to this License. The +application of the United Nations Convention on Contracts for the +International Sale of Goods is expressly excluded. + +13.7 Entire Agreement; Governing Law. This License constitutes the +entire agreement between the parties with respect to the subject +matter hereof. This License shall be governed by the laws of the +United States and the State of California, except that body of +California law concerning conflicts of law. + +Where You are located in the province of Quebec, Canada, the following +clause applies: The parties hereby confirm that they have requested +that this License and all related documents be drafted in English. Les +parties ont exige que le present contrat et tous les documents +connexes soient rediges en anglais. + +EXHIBIT A. + +"Portions Copyright (c) 1999-2003 Apple Computer, Inc. All Rights +Reserved. + +This file contains Original Code and/or Modifications of Original Code +as defined in and that are subject to the Apple Public Source License +Version 2.0 (the 'License'). You may not use this file except in +compliance with the License. Please obtain a copy of the License at +http://www.opensource.apple.com/apsl/ and read it before using this +file. + +The Original Code and all software distributed under the License are +distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER +EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, +INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. +Please see the License for the specific language governing rights and +limitations under the License." diff --git a/third_party/xnu/BUILD.gn b/third_party/xnu/BUILD.gn new file mode 100644 index 00000000..2079ecf0 --- /dev/null +++ b/third_party/xnu/BUILD.gn @@ -0,0 +1,17 @@ +# Copyright 2019 The Crashpad Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +source_set("xnu") { + sources = [ "EXTERNAL_HEADERS/mach-o/loader.h" ] +} diff --git a/third_party/xnu/EXTERNAL_HEADERS/mach-o/loader.h b/third_party/xnu/EXTERNAL_HEADERS/mach-o/loader.h new file mode 100644 index 00000000..72e8c39b --- /dev/null +++ b/third_party/xnu/EXTERNAL_HEADERS/mach-o/loader.h @@ -0,0 +1,1574 @@ +/* + * Copyright (c) 1999-2010 Apple Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ +#ifndef _MACHO_LOADER_H_ +#define _MACHO_LOADER_H_ + +/* + * This file describes the format of mach object files. + */ +#include + +/* + * is needed here for the cpu_type_t and cpu_subtype_t types + * and contains the constants for the possible values of these types. + */ +#include + +/* + * is needed here for the vm_prot_t type and contains the + * constants that are or'ed together for the possible values of this type. + */ +#include + +/* + * is expected to define the flavors of the thread + * states and the structures of those flavors for each machine. +#include +#include + */ + +/* + * The 32-bit mach header appears at the very beginning of the object file for + * 32-bit architectures. + */ +struct mach_header { + uint32_t magic; /* mach magic number identifier */ + cpu_type_t cputype; /* cpu specifier */ + cpu_subtype_t cpusubtype; /* machine specifier */ + uint32_t filetype; /* type of file */ + uint32_t ncmds; /* number of load commands */ + uint32_t sizeofcmds; /* the size of all the load commands */ + uint32_t flags; /* flags */ +}; + +/* Constant for the magic field of the mach_header (32-bit architectures) */ +#define MH_MAGIC 0xfeedface /* the mach magic number */ +#define MH_CIGAM 0xcefaedfe /* NXSwapInt(MH_MAGIC) */ + +/* + * The 64-bit mach header appears at the very beginning of object files for + * 64-bit architectures. + */ +struct mach_header_64 { + uint32_t magic; /* mach magic number identifier */ + cpu_type_t cputype; /* cpu specifier */ + cpu_subtype_t cpusubtype; /* machine specifier */ + uint32_t filetype; /* type of file */ + uint32_t ncmds; /* number of load commands */ + uint32_t sizeofcmds; /* the size of all the load commands */ + uint32_t flags; /* flags */ + uint32_t reserved; /* reserved */ +}; + +/* Constant for the magic field of the mach_header_64 (64-bit architectures) */ +#define MH_MAGIC_64 0xfeedfacf /* the 64-bit mach magic number */ +#define MH_CIGAM_64 0xcffaedfe /* NXSwapInt(MH_MAGIC_64) */ + +/* + * The layout of the file depends on the filetype. For all but the MH_OBJECT + * file type the segments are padded out and aligned on a segment alignment + * boundary for efficient demand pageing. The MH_EXECUTE, MH_FVMLIB, MH_DYLIB, + * MH_DYLINKER and MH_BUNDLE file types also have the headers included as part + * of their first segment. + * + * The file type MH_OBJECT is a compact format intended as output of the + * assembler and input (and possibly output) of the link editor (the .o + * format). All sections are in one unnamed segment with no segment padding. + * This format is used as an executable format when the file is so small the + * segment padding greatly increases its size. + * + * The file type MH_PRELOAD is an executable format intended for things that + * are not executed under the kernel (proms, stand alones, kernels, etc). The + * format can be executed under the kernel but may demand paged it and not + * preload it before execution. + * + * A core file is in MH_CORE format and can be any in an arbritray legal + * Mach-O file. + * + * Constants for the filetype field of the mach_header + */ +#define MH_OBJECT 0x1 /* relocatable object file */ +#define MH_EXECUTE 0x2 /* demand paged executable file */ +#define MH_FVMLIB 0x3 /* fixed VM shared library file */ +#define MH_CORE 0x4 /* core file */ +#define MH_PRELOAD 0x5 /* preloaded executable file */ +#define MH_DYLIB 0x6 /* dynamically bound shared library */ +#define MH_DYLINKER 0x7 /* dynamic link editor */ +#define MH_BUNDLE 0x8 /* dynamically bound bundle file */ +#define MH_DYLIB_STUB 0x9 /* shared library stub for static */ + /* linking only, no section contents */ +#define MH_DSYM 0xa /* companion file with only debug */ + /* sections */ +#define MH_KEXT_BUNDLE 0xb /* x86_64 kexts */ + +/* Constants for the flags field of the mach_header */ +#define MH_NOUNDEFS 0x1 /* the object file has no undefined + references */ +#define MH_INCRLINK 0x2 /* the object file is the output of an + incremental link against a base file + and can't be link edited again */ +#define MH_DYLDLINK 0x4 /* the object file is input for the + dynamic linker and can't be staticly + link edited again */ +#define MH_BINDATLOAD 0x8 /* the object file's undefined + references are bound by the dynamic + linker when loaded. */ +#define MH_PREBOUND 0x10 /* the file has its dynamic undefined + references prebound. */ +#define MH_SPLIT_SEGS 0x20 /* the file has its read-only and + read-write segments split */ +#define MH_LAZY_INIT 0x40 /* the shared library init routine is + to be run lazily via catching memory + faults to its writeable segments + (obsolete) */ +#define MH_TWOLEVEL 0x80 /* the image is using two-level name + space bindings */ +#define MH_FORCE_FLAT 0x100 /* the executable is forcing all images + to use flat name space bindings */ +#define MH_NOMULTIDEFS 0x200 /* this umbrella guarantees no multiple + defintions of symbols in its + sub-images so the two-level namespace + hints can always be used. */ +#define MH_NOFIXPREBINDING 0x400 /* do not have dyld notify the + prebinding agent about this + executable */ +#define MH_PREBINDABLE 0x800 /* the binary is not prebound but can + have its prebinding redone. only used + when MH_PREBOUND is not set. */ +#define MH_ALLMODSBOUND 0x1000 /* indicates that this binary binds to + all two-level namespace modules of + its dependent libraries. only used + when MH_PREBINDABLE and MH_TWOLEVEL + are both set. */ +#define MH_SUBSECTIONS_VIA_SYMBOLS 0x2000/* safe to divide up the sections into + sub-sections via symbols for dead + code stripping */ +#define MH_CANONICAL 0x4000 /* the binary has been canonicalized + via the unprebind operation */ +#define MH_WEAK_DEFINES 0x8000 /* the final linked image contains + external weak symbols */ +#define MH_BINDS_TO_WEAK 0x10000 /* the final linked image uses + weak symbols */ + +#define MH_ALLOW_STACK_EXECUTION 0x20000/* When this bit is set, all stacks + in the task will be given stack + execution privilege. Only used in + MH_EXECUTE filetypes. */ +#define MH_ROOT_SAFE 0x40000 /* When this bit is set, the binary + declares it is safe for use in + processes with uid zero */ + +#define MH_SETUID_SAFE 0x80000 /* When this bit is set, the binary + declares it is safe for use in + processes when issetugid() is true */ + +#define MH_NO_REEXPORTED_DYLIBS 0x100000 /* When this bit is set on a dylib, + the static linker does not need to + examine dependent dylibs to see + if any are re-exported */ +#define MH_PIE 0x200000 /* When this bit is set, the OS will + load the main executable at a + random address. Only used in + MH_EXECUTE filetypes. */ +#define MH_DEAD_STRIPPABLE_DYLIB 0x400000 /* Only for use on dylibs. When + linking against a dylib that + has this bit set, the static linker + will automatically not create a + LC_LOAD_DYLIB load command to the + dylib if no symbols are being + referenced from the dylib. */ +#define MH_HAS_TLV_DESCRIPTORS 0x800000 /* Contains a section of type + S_THREAD_LOCAL_VARIABLES */ + +#define MH_NO_HEAP_EXECUTION 0x1000000 /* When this bit is set, the OS will + run the main executable with + a non-executable heap even on + platforms (e.g. i386) that don't + require it. Only used in MH_EXECUTE + filetypes. */ + +#define MH_APP_EXTENSION_SAFE 0x02000000 /* The code was linked for use in an + application extension. */ + +#define MH_NLIST_OUTOFSYNC_WITH_DYLDINFO 0x04000000 /* The external symbols + listed in the nlist symbol table do + not include all the symbols listed in + the dyld info. */ + +#define MH_SIM_SUPPORT 0x08000000 /* Allow LC_MIN_VERSION_MACOS and + LC_BUILD_VERSION load commands with + the platforms macOS, iOSMac, + iOSSimulator, tvOSSimulator and + watchOSSimulator. */ + +#define MH_DYLIB_IN_CACHE 0x80000000 /* Only for use on dylibs. When this bit + is set, the dylib is part of the dyld + shared cache, rather than loose in + the filesystem. */ + +/* + * The load commands directly follow the mach_header. The total size of all + * of the commands is given by the sizeofcmds field in the mach_header. All + * load commands must have as their first two fields cmd and cmdsize. The cmd + * field is filled in with a constant for that command type. Each command type + * has a structure specifically for it. The cmdsize field is the size in bytes + * of the particular load command structure plus anything that follows it that + * is a part of the load command (i.e. section structures, strings, etc.). To + * advance to the next load command the cmdsize can be added to the offset or + * pointer of the current load command. The cmdsize for 32-bit architectures + * MUST be a multiple of 4 bytes and for 64-bit architectures MUST be a multiple + * of 8 bytes (these are forever the maximum alignment of any load commands). + * The padded bytes must be zero. All tables in the object file must also + * follow these rules so the file can be memory mapped. Otherwise the pointers + * to these tables will not work well or at all on some machines. With all + * padding zeroed like objects will compare byte for byte. + */ +struct load_command { + uint32_t cmd; /* type of load command */ + uint32_t cmdsize; /* total size of command in bytes */ +}; + +/* + * After MacOS X 10.1 when a new load command is added that is required to be + * understood by the dynamic linker for the image to execute properly the + * LC_REQ_DYLD bit will be or'ed into the load command constant. If the dynamic + * linker sees such a load command it it does not understand will issue a + * "unknown load command required for execution" error and refuse to use the + * image. Other load commands without this bit that are not understood will + * simply be ignored. + */ +#define LC_REQ_DYLD 0x80000000 + +/* Constants for the cmd field of all load commands, the type */ +#define LC_SEGMENT 0x1 /* segment of this file to be mapped */ +#define LC_SYMTAB 0x2 /* link-edit stab symbol table info */ +#define LC_SYMSEG 0x3 /* link-edit gdb symbol table info (obsolete) */ +#define LC_THREAD 0x4 /* thread */ +#define LC_UNIXTHREAD 0x5 /* unix thread (includes a stack) */ +#define LC_LOADFVMLIB 0x6 /* load a specified fixed VM shared library */ +#define LC_IDFVMLIB 0x7 /* fixed VM shared library identification */ +#define LC_IDENT 0x8 /* object identification info (obsolete) */ +#define LC_FVMFILE 0x9 /* fixed VM file inclusion (internal use) */ +#define LC_PREPAGE 0xa /* prepage command (internal use) */ +#define LC_DYSYMTAB 0xb /* dynamic link-edit symbol table info */ +#define LC_LOAD_DYLIB 0xc /* load a dynamically linked shared library */ +#define LC_ID_DYLIB 0xd /* dynamically linked shared lib ident */ +#define LC_LOAD_DYLINKER 0xe /* load a dynamic linker */ +#define LC_ID_DYLINKER 0xf /* dynamic linker identification */ +#define LC_PREBOUND_DYLIB 0x10 /* modules prebound for a dynamically */ + /* linked shared library */ +#define LC_ROUTINES 0x11 /* image routines */ +#define LC_SUB_FRAMEWORK 0x12 /* sub framework */ +#define LC_SUB_UMBRELLA 0x13 /* sub umbrella */ +#define LC_SUB_CLIENT 0x14 /* sub client */ +#define LC_SUB_LIBRARY 0x15 /* sub library */ +#define LC_TWOLEVEL_HINTS 0x16 /* two-level namespace lookup hints */ +#define LC_PREBIND_CKSUM 0x17 /* prebind checksum */ + +/* + * load a dynamically linked shared library that is allowed to be missing + * (all symbols are weak imported). + */ +#define LC_LOAD_WEAK_DYLIB (0x18 | LC_REQ_DYLD) + +#define LC_SEGMENT_64 0x19 /* 64-bit segment of this file to be + mapped */ +#define LC_ROUTINES_64 0x1a /* 64-bit image routines */ +#define LC_UUID 0x1b /* the uuid */ +#define LC_RPATH (0x1c | LC_REQ_DYLD) /* runpath additions */ +#define LC_CODE_SIGNATURE 0x1d /* local of code signature */ +#define LC_SEGMENT_SPLIT_INFO 0x1e /* local of info to split segments */ +#define LC_REEXPORT_DYLIB (0x1f | LC_REQ_DYLD) /* load and re-export dylib */ +#define LC_LAZY_LOAD_DYLIB 0x20 /* delay load of dylib until first use */ +#define LC_ENCRYPTION_INFO 0x21 /* encrypted segment information */ +#define LC_DYLD_INFO 0x22 /* compressed dyld information */ +#define LC_DYLD_INFO_ONLY (0x22|LC_REQ_DYLD) /* compressed dyld information only */ +#define LC_LOAD_UPWARD_DYLIB (0x23 | LC_REQ_DYLD) /* load upward dylib */ +#define LC_VERSION_MIN_MACOSX 0x24 /* build for MacOSX min OS version */ +#define LC_VERSION_MIN_IPHONEOS 0x25 /* build for iPhoneOS min OS version */ +#define LC_FUNCTION_STARTS 0x26 /* compressed table of function start addresses */ +#define LC_DYLD_ENVIRONMENT 0x27 /* string for dyld to treat + like environment variable */ +#define LC_MAIN (0x28|LC_REQ_DYLD) /* replacement for LC_UNIXTHREAD */ +#define LC_DATA_IN_CODE 0x29 /* table of non-instructions in __text */ +#define LC_SOURCE_VERSION 0x2A /* source version used to build binary */ +#define LC_DYLIB_CODE_SIGN_DRS 0x2B /* Code signing DRs copied from linked dylibs */ +#define LC_ENCRYPTION_INFO_64 0x2C /* 64-bit encrypted segment information */ +#define LC_LINKER_OPTION 0x2D /* linker options in MH_OBJECT files */ +#define LC_LINKER_OPTIMIZATION_HINT 0x2E /* optimization hints in MH_OBJECT files */ +#define LC_VERSION_MIN_TVOS 0x2F /* build for AppleTV min OS version */ +#define LC_VERSION_MIN_WATCHOS 0x30 /* build for Watch min OS version */ +#define LC_NOTE 0x31 /* arbitrary data included within a Mach-O file */ +#define LC_BUILD_VERSION 0x32 /* build for platform min OS version */ +#define LC_DYLD_EXPORTS_TRIE (0x33 | LC_REQ_DYLD) /* used with linkedit_data_command, payload is trie */ +#define LC_DYLD_CHAINED_FIXUPS (0x34 | LC_REQ_DYLD) /* used with linkedit_data_command */ + +/* + * A variable length string in a load command is represented by an lc_str + * union. The strings are stored just after the load command structure and + * the offset is from the start of the load command structure. The size + * of the string is reflected in the cmdsize field of the load command. + * Once again any padded bytes to bring the cmdsize field to a multiple + * of 4 bytes must be zero. + */ +union lc_str { + uint32_t offset; /* offset to the string */ +#ifndef __LP64__ + char *ptr; /* pointer to the string */ +#endif +}; + +/* + * The segment load command indicates that a part of this file is to be + * mapped into the task's address space. The size of this segment in memory, + * vmsize, maybe equal to or larger than the amount to map from this file, + * filesize. The file is mapped starting at fileoff to the beginning of + * the segment in memory, vmaddr. The rest of the memory of the segment, + * if any, is allocated zero fill on demand. The segment's maximum virtual + * memory protection and initial virtual memory protection are specified + * by the maxprot and initprot fields. If the segment has sections then the + * section structures directly follow the segment command and their size is + * reflected in cmdsize. + */ +struct segment_command { /* for 32-bit architectures */ + uint32_t cmd; /* LC_SEGMENT */ + uint32_t cmdsize; /* includes sizeof section structs */ + char segname[16]; /* segment name */ + uint32_t vmaddr; /* memory address of this segment */ + uint32_t vmsize; /* memory size of this segment */ + uint32_t fileoff; /* file offset of this segment */ + uint32_t filesize; /* amount to map from the file */ + vm_prot_t maxprot; /* maximum VM protection */ + vm_prot_t initprot; /* initial VM protection */ + uint32_t nsects; /* number of sections in segment */ + uint32_t flags; /* flags */ +}; + +/* + * The 64-bit segment load command indicates that a part of this file is to be + * mapped into a 64-bit task's address space. If the 64-bit segment has + * sections then section_64 structures directly follow the 64-bit segment + * command and their size is reflected in cmdsize. + */ +struct segment_command_64 { /* for 64-bit architectures */ + uint32_t cmd; /* LC_SEGMENT_64 */ + uint32_t cmdsize; /* includes sizeof section_64 structs */ + char segname[16]; /* segment name */ + uint64_t vmaddr; /* memory address of this segment */ + uint64_t vmsize; /* memory size of this segment */ + uint64_t fileoff; /* file offset of this segment */ + uint64_t filesize; /* amount to map from the file */ + vm_prot_t maxprot; /* maximum VM protection */ + vm_prot_t initprot; /* initial VM protection */ + uint32_t nsects; /* number of sections in segment */ + uint32_t flags; /* flags */ +}; + +/* Constants for the flags field of the segment_command */ +#define SG_HIGHVM 0x1 /* the file contents for this segment is for + the high part of the VM space, the low part + is zero filled (for stacks in core files) */ +#define SG_FVMLIB 0x2 /* this segment is the VM that is allocated by + a fixed VM library, for overlap checking in + the link editor */ +#define SG_NORELOC 0x4 /* this segment has nothing that was relocated + in it and nothing relocated to it, that is + it maybe safely replaced without relocation*/ +#define SG_PROTECTED_VERSION_1 0x8 /* This segment is protected. If the + segment starts at file offset 0, the + first page of the segment is not + protected. All other pages of the + segment are protected. */ +#define SG_READ_ONLY 0x10 /* This segment is made read-only after fixups */ + + + +/* + * A segment is made up of zero or more sections. Non-MH_OBJECT files have + * all of their segments with the proper sections in each, and padded to the + * specified segment alignment when produced by the link editor. The first + * segment of a MH_EXECUTE and MH_FVMLIB format file contains the mach_header + * and load commands of the object file before its first section. The zero + * fill sections are always last in their segment (in all formats). This + * allows the zeroed segment padding to be mapped into memory where zero fill + * sections might be. The gigabyte zero fill sections, those with the section + * type S_GB_ZEROFILL, can only be in a segment with sections of this type. + * These segments are then placed after all other segments. + * + * The MH_OBJECT format has all of its sections in one segment for + * compactness. There is no padding to a specified segment boundary and the + * mach_header and load commands are not part of the segment. + * + * Sections with the same section name, sectname, going into the same segment, + * segname, are combined by the link editor. The resulting section is aligned + * to the maximum alignment of the combined sections and is the new section's + * alignment. The combined sections are aligned to their original alignment in + * the combined section. Any padded bytes to get the specified alignment are + * zeroed. + * + * The format of the relocation entries referenced by the reloff and nreloc + * fields of the section structure for mach object files is described in the + * header file . + */ +struct section { /* for 32-bit architectures */ + char sectname[16]; /* name of this section */ + char segname[16]; /* segment this section goes in */ + uint32_t addr; /* memory address of this section */ + uint32_t size; /* size in bytes of this section */ + uint32_t offset; /* file offset of this section */ + uint32_t align; /* section alignment (power of 2) */ + uint32_t reloff; /* file offset of relocation entries */ + uint32_t nreloc; /* number of relocation entries */ + uint32_t flags; /* flags (section type and attributes)*/ + uint32_t reserved1; /* reserved (for offset or index) */ + uint32_t reserved2; /* reserved (for count or sizeof) */ +}; + +struct section_64 { /* for 64-bit architectures */ + char sectname[16]; /* name of this section */ + char segname[16]; /* segment this section goes in */ + uint64_t addr; /* memory address of this section */ + uint64_t size; /* size in bytes of this section */ + uint32_t offset; /* file offset of this section */ + uint32_t align; /* section alignment (power of 2) */ + uint32_t reloff; /* file offset of relocation entries */ + uint32_t nreloc; /* number of relocation entries */ + uint32_t flags; /* flags (section type and attributes)*/ + uint32_t reserved1; /* reserved (for offset or index) */ + uint32_t reserved2; /* reserved (for count or sizeof) */ + uint32_t reserved3; /* reserved */ +}; + +/* + * The flags field of a section structure is separated into two parts a section + * type and section attributes. The section types are mutually exclusive (it + * can only have one type) but the section attributes are not (it may have more + * than one attribute). + */ +#define SECTION_TYPE 0x000000ff /* 256 section types */ +#define SECTION_ATTRIBUTES 0xffffff00 /* 24 section attributes */ + +/* Constants for the type of a section */ +#define S_REGULAR 0x0 /* regular section */ +#define S_ZEROFILL 0x1 /* zero fill on demand section */ +#define S_CSTRING_LITERALS 0x2 /* section with only literal C strings*/ +#define S_4BYTE_LITERALS 0x3 /* section with only 4 byte literals */ +#define S_8BYTE_LITERALS 0x4 /* section with only 8 byte literals */ +#define S_LITERAL_POINTERS 0x5 /* section with only pointers to */ + /* literals */ +/* + * For the two types of symbol pointers sections and the symbol stubs section + * they have indirect symbol table entries. For each of the entries in the + * section the indirect symbol table entries, in corresponding order in the + * indirect symbol table, start at the index stored in the reserved1 field + * of the section structure. Since the indirect symbol table entries + * correspond to the entries in the section the number of indirect symbol table + * entries is inferred from the size of the section divided by the size of the + * entries in the section. For symbol pointers sections the size of the entries + * in the section is 4 bytes and for symbol stubs sections the byte size of the + * stubs is stored in the reserved2 field of the section structure. + */ +#define S_NON_LAZY_SYMBOL_POINTERS 0x6 /* section with only non-lazy + symbol pointers */ +#define S_LAZY_SYMBOL_POINTERS 0x7 /* section with only lazy symbol + pointers */ +#define S_SYMBOL_STUBS 0x8 /* section with only symbol + stubs, byte size of stub in + the reserved2 field */ +#define S_MOD_INIT_FUNC_POINTERS 0x9 /* section with only function + pointers for initialization*/ +#define S_MOD_TERM_FUNC_POINTERS 0xa /* section with only function + pointers for termination */ +#define S_COALESCED 0xb /* section contains symbols that + are to be coalesced */ +#define S_GB_ZEROFILL 0xc /* zero fill on demand section + (that can be larger than 4 + gigabytes) */ +#define S_INTERPOSING 0xd /* section with only pairs of + function pointers for + interposing */ +#define S_16BYTE_LITERALS 0xe /* section with only 16 byte + literals */ +#define S_DTRACE_DOF 0xf /* section contains + DTrace Object Format */ +#define S_LAZY_DYLIB_SYMBOL_POINTERS 0x10 /* section with only lazy + symbol pointers to lazy + loaded dylibs */ +/* + * Section types to support thread local variables + */ +#define S_THREAD_LOCAL_REGULAR 0x11 /* template of initial + values for TLVs */ +#define S_THREAD_LOCAL_ZEROFILL 0x12 /* template of initial + values for TLVs */ +#define S_THREAD_LOCAL_VARIABLES 0x13 /* TLV descriptors */ +#define S_THREAD_LOCAL_VARIABLE_POINTERS 0x14 /* pointers to TLV + descriptors */ +#define S_THREAD_LOCAL_INIT_FUNCTION_POINTERS 0x15 /* functions to call + to initialize TLV + values */ +#define S_INIT_FUNC_OFFSETS 0x16 /* 32-bit offsets to + initializers */ + +/* + * Constants for the section attributes part of the flags field of a section + * structure. + */ +#define SECTION_ATTRIBUTES_USR 0xff000000 /* User setable attributes */ +#define S_ATTR_PURE_INSTRUCTIONS 0x80000000 /* section contains only true + machine instructions */ +#define S_ATTR_NO_TOC 0x40000000 /* section contains coalesced + symbols that are not to be + in a ranlib table of + contents */ +#define S_ATTR_STRIP_STATIC_SYMS 0x20000000 /* ok to strip static symbols + in this section in files + with the MH_DYLDLINK flag */ +#define S_ATTR_NO_DEAD_STRIP 0x10000000 /* no dead stripping */ +#define S_ATTR_LIVE_SUPPORT 0x08000000 /* blocks are live if they + reference live blocks */ +#define S_ATTR_SELF_MODIFYING_CODE 0x04000000 /* Used with i386 code stubs + written on by dyld */ +/* + * If a segment contains any sections marked with S_ATTR_DEBUG then all + * sections in that segment must have this attribute. No section other than + * a section marked with this attribute may reference the contents of this + * section. A section with this attribute may contain no symbols and must have + * a section type S_REGULAR. The static linker will not copy section contents + * from sections with this attribute into its output file. These sections + * generally contain DWARF debugging info. + */ +#define S_ATTR_DEBUG 0x02000000 /* a debug section */ +#define SECTION_ATTRIBUTES_SYS 0x00ffff00 /* system setable attributes */ +#define S_ATTR_SOME_INSTRUCTIONS 0x00000400 /* section contains some + machine instructions */ +#define S_ATTR_EXT_RELOC 0x00000200 /* section has external + relocation entries */ +#define S_ATTR_LOC_RELOC 0x00000100 /* section has local + relocation entries */ + + +/* + * The names of segments and sections in them are mostly meaningless to the + * link-editor. But there are few things to support traditional UNIX + * executables that require the link-editor and assembler to use some names + * agreed upon by convention. + * + * The initial protection of the "__TEXT" segment has write protection turned + * off (not writeable). + * + * The link-editor will allocate common symbols at the end of the "__common" + * section in the "__DATA" segment. It will create the section and segment + * if needed. + */ + +/* The currently known segment names and the section names in those segments */ + +#define SEG_PAGEZERO "__PAGEZERO" /* the pagezero segment which has no */ + /* protections and catches NULL */ + /* references for MH_EXECUTE files */ + + +#define SEG_TEXT "__TEXT" /* the tradition UNIX text segment */ +#define SECT_TEXT "__text" /* the real text part of the text */ + /* section no headers, and no padding */ +#define SECT_FVMLIB_INIT0 "__fvmlib_init0" /* the fvmlib initialization */ + /* section */ +#define SECT_FVMLIB_INIT1 "__fvmlib_init1" /* the section following the */ + /* fvmlib initialization */ + /* section */ + +#define SEG_DATA "__DATA" /* the tradition UNIX data segment */ +#define SECT_DATA "__data" /* the real initialized data section */ + /* no padding, no bss overlap */ +#define SECT_BSS "__bss" /* the real uninitialized data section*/ + /* no padding */ +#define SECT_COMMON "__common" /* the section common symbols are */ + /* allocated in by the link editor */ + +#define SEG_OBJC "__OBJC" /* objective-C runtime segment */ +#define SECT_OBJC_SYMBOLS "__symbol_table" /* symbol table */ +#define SECT_OBJC_MODULES "__module_info" /* module information */ +#define SECT_OBJC_STRINGS "__selector_strs" /* string table */ +#define SECT_OBJC_REFS "__selector_refs" /* string table */ + +#define SEG_ICON "__ICON" /* the icon segment */ +#define SECT_ICON_HEADER "__header" /* the icon headers */ +#define SECT_ICON_TIFF "__tiff" /* the icons in tiff format */ + +#define SEG_LINKEDIT "__LINKEDIT" /* the segment containing all structs */ + /* created and maintained by the link */ + /* editor. Created with -seglinkedit */ + /* option to ld(1) for MH_EXECUTE and */ + /* FVMLIB file types only */ + +#define SEG_UNIXSTACK "__UNIXSTACK" /* the unix stack segment */ + +#define SEG_IMPORT "__IMPORT" /* the segment for the self (dyld) */ + /* modifing code stubs that has read, */ + /* write and execute permissions */ + +/* + * Fixed virtual memory shared libraries are identified by two things. The + * target pathname (the name of the library as found for execution), and the + * minor version number. The address of where the headers are loaded is in + * header_addr. (THIS IS OBSOLETE and no longer supported). + */ +struct fvmlib { + union lc_str name; /* library's target pathname */ + uint32_t minor_version; /* library's minor version number */ + uint32_t header_addr; /* library's header address */ +}; + +/* + * A fixed virtual shared library (filetype == MH_FVMLIB in the mach header) + * contains a fvmlib_command (cmd == LC_IDFVMLIB) to identify the library. + * An object that uses a fixed virtual shared library also contains a + * fvmlib_command (cmd == LC_LOADFVMLIB) for each library it uses. + * (THIS IS OBSOLETE and no longer supported). + */ +struct fvmlib_command { + uint32_t cmd; /* LC_IDFVMLIB or LC_LOADFVMLIB */ + uint32_t cmdsize; /* includes pathname string */ + struct fvmlib fvmlib; /* the library identification */ +}; + +/* + * Dynamicly linked shared libraries are identified by two things. The + * pathname (the name of the library as found for execution), and the + * compatibility version number. The pathname must match and the compatibility + * number in the user of the library must be greater than or equal to the + * library being used. The time stamp is used to record the time a library was + * built and copied into user so it can be use to determined if the library used + * at runtime is exactly the same as used to built the program. + */ +struct dylib { + union lc_str name; /* library's path name */ + uint32_t timestamp; /* library's build time stamp */ + uint32_t current_version; /* library's current version number */ + uint32_t compatibility_version; /* library's compatibility vers number*/ +}; + +/* + * A dynamically linked shared library (filetype == MH_DYLIB in the mach header) + * contains a dylib_command (cmd == LC_ID_DYLIB) to identify the library. + * An object that uses a dynamically linked shared library also contains a + * dylib_command (cmd == LC_LOAD_DYLIB, LC_LOAD_WEAK_DYLIB, or + * LC_REEXPORT_DYLIB) for each library it uses. + */ +struct dylib_command { + uint32_t cmd; /* LC_ID_DYLIB, LC_LOAD_{,WEAK_}DYLIB, + LC_REEXPORT_DYLIB */ + uint32_t cmdsize; /* includes pathname string */ + struct dylib dylib; /* the library identification */ +}; + +/* + * A dynamically linked shared library may be a subframework of an umbrella + * framework. If so it will be linked with "-umbrella umbrella_name" where + * Where "umbrella_name" is the name of the umbrella framework. A subframework + * can only be linked against by its umbrella framework or other subframeworks + * that are part of the same umbrella framework. Otherwise the static link + * editor produces an error and states to link against the umbrella framework. + * The name of the umbrella framework for subframeworks is recorded in the + * following structure. + */ +struct sub_framework_command { + uint32_t cmd; /* LC_SUB_FRAMEWORK */ + uint32_t cmdsize; /* includes umbrella string */ + union lc_str umbrella; /* the umbrella framework name */ +}; + +/* + * For dynamically linked shared libraries that are subframework of an umbrella + * framework they can allow clients other than the umbrella framework or other + * subframeworks in the same umbrella framework. To do this the subframework + * is built with "-allowable_client client_name" and an LC_SUB_CLIENT load + * command is created for each -allowable_client flag. The client_name is + * usually a framework name. It can also be a name used for bundles clients + * where the bundle is built with "-client_name client_name". + */ +struct sub_client_command { + uint32_t cmd; /* LC_SUB_CLIENT */ + uint32_t cmdsize; /* includes client string */ + union lc_str client; /* the client name */ +}; + +/* + * A dynamically linked shared library may be a sub_umbrella of an umbrella + * framework. If so it will be linked with "-sub_umbrella umbrella_name" where + * Where "umbrella_name" is the name of the sub_umbrella framework. When + * staticly linking when -twolevel_namespace is in effect a twolevel namespace + * umbrella framework will only cause its subframeworks and those frameworks + * listed as sub_umbrella frameworks to be implicited linked in. Any other + * dependent dynamic libraries will not be linked it when -twolevel_namespace + * is in effect. The primary library recorded by the static linker when + * resolving a symbol in these libraries will be the umbrella framework. + * Zero or more sub_umbrella frameworks may be use by an umbrella framework. + * The name of a sub_umbrella framework is recorded in the following structure. + */ +struct sub_umbrella_command { + uint32_t cmd; /* LC_SUB_UMBRELLA */ + uint32_t cmdsize; /* includes sub_umbrella string */ + union lc_str sub_umbrella; /* the sub_umbrella framework name */ +}; + +/* + * A dynamically linked shared library may be a sub_library of another shared + * library. If so it will be linked with "-sub_library library_name" where + * Where "library_name" is the name of the sub_library shared library. When + * staticly linking when -twolevel_namespace is in effect a twolevel namespace + * shared library will only cause its subframeworks and those frameworks + * listed as sub_umbrella frameworks and libraries listed as sub_libraries to + * be implicited linked in. Any other dependent dynamic libraries will not be + * linked it when -twolevel_namespace is in effect. The primary library + * recorded by the static linker when resolving a symbol in these libraries + * will be the umbrella framework (or dynamic library). Zero or more sub_library + * shared libraries may be use by an umbrella framework or (or dynamic library). + * The name of a sub_library framework is recorded in the following structure. + * For example /usr/lib/libobjc_profile.A.dylib would be recorded as "libobjc". + */ +struct sub_library_command { + uint32_t cmd; /* LC_SUB_LIBRARY */ + uint32_t cmdsize; /* includes sub_library string */ + union lc_str sub_library; /* the sub_library name */ +}; + +/* + * A program (filetype == MH_EXECUTE) that is + * prebound to its dynamic libraries has one of these for each library that + * the static linker used in prebinding. It contains a bit vector for the + * modules in the library. The bits indicate which modules are bound (1) and + * which are not (0) from the library. The bit for module 0 is the low bit + * of the first byte. So the bit for the Nth module is: + * (linked_modules[N/8] >> N%8) & 1 + */ +struct prebound_dylib_command { + uint32_t cmd; /* LC_PREBOUND_DYLIB */ + uint32_t cmdsize; /* includes strings */ + union lc_str name; /* library's path name */ + uint32_t nmodules; /* number of modules in library */ + union lc_str linked_modules; /* bit vector of linked modules */ +}; + +/* + * A program that uses a dynamic linker contains a dylinker_command to identify + * the name of the dynamic linker (LC_LOAD_DYLINKER). And a dynamic linker + * contains a dylinker_command to identify the dynamic linker (LC_ID_DYLINKER). + * A file can have at most one of these. + * This struct is also used for the LC_DYLD_ENVIRONMENT load command and + * contains string for dyld to treat like environment variable. + */ +struct dylinker_command { + uint32_t cmd; /* LC_ID_DYLINKER, LC_LOAD_DYLINKER or + LC_DYLD_ENVIRONMENT */ + uint32_t cmdsize; /* includes pathname string */ + union lc_str name; /* dynamic linker's path name */ +}; + +/* + * Thread commands contain machine-specific data structures suitable for + * use in the thread state primitives. The machine specific data structures + * follow the struct thread_command as follows. + * Each flavor of machine specific data structure is preceded by an uint32_t + * constant for the flavor of that data structure, an uint32_t that is the + * count of uint32_t's of the size of the state data structure and then + * the state data structure follows. This triple may be repeated for many + * flavors. The constants for the flavors, counts and state data structure + * definitions are expected to be in the header file . + * These machine specific data structures sizes must be multiples of + * 4 bytes. The cmdsize reflects the total size of the thread_command + * and all of the sizes of the constants for the flavors, counts and state + * data structures. + * + * For executable objects that are unix processes there will be one + * thread_command (cmd == LC_UNIXTHREAD) created for it by the link-editor. + * This is the same as a LC_THREAD, except that a stack is automatically + * created (based on the shell's limit for the stack size). Command arguments + * and environment variables are copied onto that stack. + */ +struct thread_command { + uint32_t cmd; /* LC_THREAD or LC_UNIXTHREAD */ + uint32_t cmdsize; /* total size of this command */ + /* uint32_t flavor flavor of thread state */ + /* uint32_t count count of uint32_t's in thread state */ + /* struct XXX_thread_state state thread state for this flavor */ + /* ... */ +}; + +/* + * The routines command contains the address of the dynamic shared library + * initialization routine and an index into the module table for the module + * that defines the routine. Before any modules are used from the library the + * dynamic linker fully binds the module that defines the initialization routine + * and then calls it. This gets called before any module initialization + * routines (used for C++ static constructors) in the library. + */ +struct routines_command { /* for 32-bit architectures */ + uint32_t cmd; /* LC_ROUTINES */ + uint32_t cmdsize; /* total size of this command */ + uint32_t init_address; /* address of initialization routine */ + uint32_t init_module; /* index into the module table that */ + /* the init routine is defined in */ + uint32_t reserved1; + uint32_t reserved2; + uint32_t reserved3; + uint32_t reserved4; + uint32_t reserved5; + uint32_t reserved6; +}; + +/* + * The 64-bit routines command. Same use as above. + */ +struct routines_command_64 { /* for 64-bit architectures */ + uint32_t cmd; /* LC_ROUTINES_64 */ + uint32_t cmdsize; /* total size of this command */ + uint64_t init_address; /* address of initialization routine */ + uint64_t init_module; /* index into the module table that */ + /* the init routine is defined in */ + uint64_t reserved1; + uint64_t reserved2; + uint64_t reserved3; + uint64_t reserved4; + uint64_t reserved5; + uint64_t reserved6; +}; + +/* + * The symtab_command contains the offsets and sizes of the link-edit 4.3BSD + * "stab" style symbol table information as described in the header files + * and . + */ +struct symtab_command { + uint32_t cmd; /* LC_SYMTAB */ + uint32_t cmdsize; /* sizeof(struct symtab_command) */ + uint32_t symoff; /* symbol table offset */ + uint32_t nsyms; /* number of symbol table entries */ + uint32_t stroff; /* string table offset */ + uint32_t strsize; /* string table size in bytes */ +}; + +/* + * This is the second set of the symbolic information which is used to support + * the data structures for the dynamically link editor. + * + * The original set of symbolic information in the symtab_command which contains + * the symbol and string tables must also be present when this load command is + * present. When this load command is present the symbol table is organized + * into three groups of symbols: + * local symbols (static and debugging symbols) - grouped by module + * defined external symbols - grouped by module (sorted by name if not lib) + * undefined external symbols (sorted by name if MH_BINDATLOAD is not set, + * and in order the were seen by the static + * linker if MH_BINDATLOAD is set) + * In this load command there are offsets and counts to each of the three groups + * of symbols. + * + * This load command contains a the offsets and sizes of the following new + * symbolic information tables: + * table of contents + * module table + * reference symbol table + * indirect symbol table + * The first three tables above (the table of contents, module table and + * reference symbol table) are only present if the file is a dynamically linked + * shared library. For executable and object modules, which are files + * containing only one module, the information that would be in these three + * tables is determined as follows: + * table of contents - the defined external symbols are sorted by name + * module table - the file contains only one module so everything in the + * file is part of the module. + * reference symbol table - is the defined and undefined external symbols + * + * For dynamically linked shared library files this load command also contains + * offsets and sizes to the pool of relocation entries for all sections + * separated into two groups: + * external relocation entries + * local relocation entries + * For executable and object modules the relocation entries continue to hang + * off the section structures. + */ +struct dysymtab_command { + uint32_t cmd; /* LC_DYSYMTAB */ + uint32_t cmdsize; /* sizeof(struct dysymtab_command) */ + + /* + * The symbols indicated by symoff and nsyms of the LC_SYMTAB load command + * are grouped into the following three groups: + * local symbols (further grouped by the module they are from) + * defined external symbols (further grouped by the module they are from) + * undefined symbols + * + * The local symbols are used only for debugging. The dynamic binding + * process may have to use them to indicate to the debugger the local + * symbols for a module that is being bound. + * + * The last two groups are used by the dynamic binding process to do the + * binding (indirectly through the module table and the reference symbol + * table when this is a dynamically linked shared library file). + */ + uint32_t ilocalsym; /* index to local symbols */ + uint32_t nlocalsym; /* number of local symbols */ + + uint32_t iextdefsym;/* index to externally defined symbols */ + uint32_t nextdefsym;/* number of externally defined symbols */ + + uint32_t iundefsym; /* index to undefined symbols */ + uint32_t nundefsym; /* number of undefined symbols */ + + /* + * For the for the dynamic binding process to find which module a symbol + * is defined in the table of contents is used (analogous to the ranlib + * structure in an archive) which maps defined external symbols to modules + * they are defined in. This exists only in a dynamically linked shared + * library file. For executable and object modules the defined external + * symbols are sorted by name and is use as the table of contents. + */ + uint32_t tocoff; /* file offset to table of contents */ + uint32_t ntoc; /* number of entries in table of contents */ + + /* + * To support dynamic binding of "modules" (whole object files) the symbol + * table must reflect the modules that the file was created from. This is + * done by having a module table that has indexes and counts into the merged + * tables for each module. The module structure that these two entries + * refer to is described below. This exists only in a dynamically linked + * shared library file. For executable and object modules the file only + * contains one module so everything in the file belongs to the module. + */ + uint32_t modtaboff; /* file offset to module table */ + uint32_t nmodtab; /* number of module table entries */ + + /* + * To support dynamic module binding the module structure for each module + * indicates the external references (defined and undefined) each module + * makes. For each module there is an offset and a count into the + * reference symbol table for the symbols that the module references. + * This exists only in a dynamically linked shared library file. For + * executable and object modules the defined external symbols and the + * undefined external symbols indicates the external references. + */ + uint32_t extrefsymoff; /* offset to referenced symbol table */ + uint32_t nextrefsyms; /* number of referenced symbol table entries */ + + /* + * The sections that contain "symbol pointers" and "routine stubs" have + * indexes and (implied counts based on the size of the section and fixed + * size of the entry) into the "indirect symbol" table for each pointer + * and stub. For every section of these two types the index into the + * indirect symbol table is stored in the section header in the field + * reserved1. An indirect symbol table entry is simply a 32bit index into + * the symbol table to the symbol that the pointer or stub is referring to. + * The indirect symbol table is ordered to match the entries in the section. + */ + uint32_t indirectsymoff; /* file offset to the indirect symbol table */ + uint32_t nindirectsyms; /* number of indirect symbol table entries */ + + /* + * To support relocating an individual module in a library file quickly the + * external relocation entries for each module in the library need to be + * accessed efficiently. Since the relocation entries can't be accessed + * through the section headers for a library file they are separated into + * groups of local and external entries further grouped by module. In this + * case the presents of this load command who's extreloff, nextrel, + * locreloff and nlocrel fields are non-zero indicates that the relocation + * entries of non-merged sections are not referenced through the section + * structures (and the reloff and nreloc fields in the section headers are + * set to zero). + * + * Since the relocation entries are not accessed through the section headers + * this requires the r_address field to be something other than a section + * offset to identify the item to be relocated. In this case r_address is + * set to the offset from the vmaddr of the first LC_SEGMENT command. + * For MH_SPLIT_SEGS images r_address is set to the the offset from the + * vmaddr of the first read-write LC_SEGMENT command. + * + * The relocation entries are grouped by module and the module table + * entries have indexes and counts into them for the group of external + * relocation entries for that the module. + * + * For sections that are merged across modules there must not be any + * remaining external relocation entries for them (for merged sections + * remaining relocation entries must be local). + */ + uint32_t extreloff; /* offset to external relocation entries */ + uint32_t nextrel; /* number of external relocation entries */ + + /* + * All the local relocation entries are grouped together (they are not + * grouped by their module since they are only used if the object is moved + * from it staticly link edited address). + */ + uint32_t locreloff; /* offset to local relocation entries */ + uint32_t nlocrel; /* number of local relocation entries */ + +}; + +/* + * An indirect symbol table entry is simply a 32bit index into the symbol table + * to the symbol that the pointer or stub is refering to. Unless it is for a + * non-lazy symbol pointer section for a defined symbol which strip(1) as + * removed. In which case it has the value INDIRECT_SYMBOL_LOCAL. If the + * symbol was also absolute INDIRECT_SYMBOL_ABS is or'ed with that. + */ +#define INDIRECT_SYMBOL_LOCAL 0x80000000 +#define INDIRECT_SYMBOL_ABS 0x40000000 + + +/* a table of contents entry */ +struct dylib_table_of_contents { + uint32_t symbol_index; /* the defined external symbol + (index into the symbol table) */ + uint32_t module_index; /* index into the module table this symbol + is defined in */ +}; + +/* a module table entry */ +struct dylib_module { + uint32_t module_name; /* the module name (index into string table) */ + + uint32_t iextdefsym; /* index into externally defined symbols */ + uint32_t nextdefsym; /* number of externally defined symbols */ + uint32_t irefsym; /* index into reference symbol table */ + uint32_t nrefsym; /* number of reference symbol table entries */ + uint32_t ilocalsym; /* index into symbols for local symbols */ + uint32_t nlocalsym; /* number of local symbols */ + + uint32_t iextrel; /* index into external relocation entries */ + uint32_t nextrel; /* number of external relocation entries */ + + uint32_t iinit_iterm; /* low 16 bits are the index into the init + section, high 16 bits are the index into + the term section */ + uint32_t ninit_nterm; /* low 16 bits are the number of init section + entries, high 16 bits are the number of + term section entries */ + + uint32_t /* for this module address of the start of */ + objc_module_info_addr; /* the (__OBJC,__module_info) section */ + uint32_t /* for this module size of */ + objc_module_info_size; /* the (__OBJC,__module_info) section */ +}; + +/* a 64-bit module table entry */ +struct dylib_module_64 { + uint32_t module_name; /* the module name (index into string table) */ + + uint32_t iextdefsym; /* index into externally defined symbols */ + uint32_t nextdefsym; /* number of externally defined symbols */ + uint32_t irefsym; /* index into reference symbol table */ + uint32_t nrefsym; /* number of reference symbol table entries */ + uint32_t ilocalsym; /* index into symbols for local symbols */ + uint32_t nlocalsym; /* number of local symbols */ + + uint32_t iextrel; /* index into external relocation entries */ + uint32_t nextrel; /* number of external relocation entries */ + + uint32_t iinit_iterm; /* low 16 bits are the index into the init + section, high 16 bits are the index into + the term section */ + uint32_t ninit_nterm; /* low 16 bits are the number of init section + entries, high 16 bits are the number of + term section entries */ + + uint32_t /* for this module size of */ + objc_module_info_size; /* the (__OBJC,__module_info) section */ + uint64_t /* for this module address of the start of */ + objc_module_info_addr; /* the (__OBJC,__module_info) section */ +}; + +/* + * The entries in the reference symbol table are used when loading the module + * (both by the static and dynamic link editors) and if the module is unloaded + * or replaced. Therefore all external symbols (defined and undefined) are + * listed in the module's reference table. The flags describe the type of + * reference that is being made. The constants for the flags are defined in + * as they are also used for symbol table entries. + */ +struct dylib_reference { + uint32_t isym:24, /* index into the symbol table */ + flags:8; /* flags to indicate the type of reference */ +}; + +/* + * The twolevel_hints_command contains the offset and number of hints in the + * two-level namespace lookup hints table. + */ +struct twolevel_hints_command { + uint32_t cmd; /* LC_TWOLEVEL_HINTS */ + uint32_t cmdsize; /* sizeof(struct twolevel_hints_command) */ + uint32_t offset; /* offset to the hint table */ + uint32_t nhints; /* number of hints in the hint table */ +}; + +/* + * The entries in the two-level namespace lookup hints table are twolevel_hint + * structs. These provide hints to the dynamic link editor where to start + * looking for an undefined symbol in a two-level namespace image. The + * isub_image field is an index into the sub-images (sub-frameworks and + * sub-umbrellas list) that made up the two-level image that the undefined + * symbol was found in when it was built by the static link editor. If + * isub-image is 0 the the symbol is expected to be defined in library and not + * in the sub-images. If isub-image is non-zero it is an index into the array + * of sub-images for the umbrella with the first index in the sub-images being + * 1. The array of sub-images is the ordered list of sub-images of the umbrella + * that would be searched for a symbol that has the umbrella recorded as its + * primary library. The table of contents index is an index into the + * library's table of contents. This is used as the starting point of the + * binary search or a directed linear search. + */ +struct twolevel_hint { + uint32_t + isub_image:8, /* index into the sub images */ + itoc:24; /* index into the table of contents */ +}; + +/* + * The prebind_cksum_command contains the value of the original check sum for + * prebound files or zero. When a prebound file is first created or modified + * for other than updating its prebinding information the value of the check sum + * is set to zero. When the file has it prebinding re-done and if the value of + * the check sum is zero the original check sum is calculated and stored in + * cksum field of this load command in the output file. If when the prebinding + * is re-done and the cksum field is non-zero it is left unchanged from the + * input file. + */ +struct prebind_cksum_command { + uint32_t cmd; /* LC_PREBIND_CKSUM */ + uint32_t cmdsize; /* sizeof(struct prebind_cksum_command) */ + uint32_t cksum; /* the check sum or zero */ +}; + +/* + * The uuid load command contains a single 128-bit unique random number that + * identifies an object produced by the static link editor. + */ +struct uuid_command { + uint32_t cmd; /* LC_UUID */ + uint32_t cmdsize; /* sizeof(struct uuid_command) */ + uint8_t uuid[16]; /* the 128-bit uuid */ +}; + +/* + * The rpath_command contains a path which at runtime should be added to + * the current run path used to find @rpath prefixed dylibs. + */ +struct rpath_command { + uint32_t cmd; /* LC_RPATH */ + uint32_t cmdsize; /* includes string */ + union lc_str path; /* path to add to run path */ +}; + +/* + * The linkedit_data_command contains the offsets and sizes of a blob + * of data in the __LINKEDIT segment. + */ +struct linkedit_data_command { + uint32_t cmd; /* LC_CODE_SIGNATURE, LC_SEGMENT_SPLIT_INFO, + LC_FUNCTION_STARTS, LC_DATA_IN_CODE, + LC_DYLIB_CODE_SIGN_DRS, + LC_LINKER_OPTIMIZATION_HINT, + LC_DYLD_EXPORTS_TRIE, or + LC_DYLD_CHAINED_FIXUPS. */ + uint32_t cmdsize; /* sizeof(struct linkedit_data_command) */ + uint32_t dataoff; /* file offset of data in __LINKEDIT segment */ + uint32_t datasize; /* file size of data in __LINKEDIT segment */ +}; + +/* + * The encryption_info_command contains the file offset and size of an + * of an encrypted segment. + */ +struct encryption_info_command { + uint32_t cmd; /* LC_ENCRYPTION_INFO */ + uint32_t cmdsize; /* sizeof(struct encryption_info_command) */ + uint32_t cryptoff; /* file offset of encrypted range */ + uint32_t cryptsize; /* file size of encrypted range */ + uint32_t cryptid; /* which enryption system, + 0 means not-encrypted yet */ +}; + +/* + * The encryption_info_command_64 contains the file offset and size of an + * of an encrypted segment (for use in x86_64 targets). + */ +struct encryption_info_command_64 { + uint32_t cmd; /* LC_ENCRYPTION_INFO_64 */ + uint32_t cmdsize; /* sizeof(struct encryption_info_command_64) */ + uint32_t cryptoff; /* file offset of encrypted range */ + uint32_t cryptsize; /* file size of encrypted range */ + uint32_t cryptid; /* which enryption system, + 0 means not-encrypted yet */ + uint32_t pad; /* padding to make this struct's size a multiple + of 8 bytes */ +}; + +/* + * The version_min_command contains the min OS version on which this + * binary was built to run. + */ +struct version_min_command { + uint32_t cmd; /* LC_VERSION_MIN_MACOSX or + LC_VERSION_MIN_IPHONEOS or + LC_VERSION_MIN_WATCHOS or + LC_VERSION_MIN_TVOS */ + uint32_t cmdsize; /* sizeof(struct min_version_command) */ + uint32_t version; /* X.Y.Z is encoded in nibbles xxxx.yy.zz */ + uint32_t sdk; /* X.Y.Z is encoded in nibbles xxxx.yy.zz */ +}; + +/* + * The build_version_command contains the min OS version on which this + * binary was built to run for its platform. The list of known platforms and + * tool values following it. + */ +struct build_version_command { + uint32_t cmd; /* LC_BUILD_VERSION */ + uint32_t cmdsize; /* sizeof(struct build_version_command) plus */ + /* ntools * sizeof(struct build_tool_version) */ + uint32_t platform; /* platform */ + uint32_t minos; /* X.Y.Z is encoded in nibbles xxxx.yy.zz */ + uint32_t sdk; /* X.Y.Z is encoded in nibbles xxxx.yy.zz */ + uint32_t ntools; /* number of tool entries following this */ +}; + +struct build_tool_version { + uint32_t tool; /* enum for the tool */ + uint32_t version; /* version number of the tool */ +}; + +/* Known values for the platform field above. */ +#define PLATFORM_MACOS 1 +#define PLATFORM_IOS 2 +#define PLATFORM_TVOS 3 +#define PLATFORM_WATCHOS 4 +#define PLATFORM_BRIDGEOS 5 +#define PLATFORM_IOSMAC 6 +#define PLATFORM_IOSSIMULATOR 7 +#define PLATFORM_TVOSSIMULATOR 8 +#define PLATFORM_WATCHOSSIMULATOR 9 +#define PLATFORM_DRIVERKIT 10 + +/* Known values for the tool field above. */ +#define TOOL_CLANG 1 +#define TOOL_SWIFT 2 +#define TOOL_LD 3 + +/* + * The dyld_info_command contains the file offsets and sizes of + * the new compressed form of the information dyld needs to + * load the image. This information is used by dyld on Mac OS X + * 10.6 and later. All information pointed to by this command + * is encoded using byte streams, so no endian swapping is needed + * to interpret it. + */ +struct dyld_info_command { + uint32_t cmd; /* LC_DYLD_INFO or LC_DYLD_INFO_ONLY */ + uint32_t cmdsize; /* sizeof(struct dyld_info_command) */ + + /* + * Dyld rebases an image whenever dyld loads it at an address different + * from its preferred address. The rebase information is a stream + * of byte sized opcodes whose symbolic names start with REBASE_OPCODE_. + * Conceptually the rebase information is a table of tuples: + * + * The opcodes are a compressed way to encode the table by only + * encoding when a column changes. In addition simple patterns + * like "every n'th offset for m times" can be encoded in a few + * bytes. + */ + uint32_t rebase_off; /* file offset to rebase info */ + uint32_t rebase_size; /* size of rebase info */ + + /* + * Dyld binds an image during the loading process, if the image + * requires any pointers to be initialized to symbols in other images. + * The bind information is a stream of byte sized + * opcodes whose symbolic names start with BIND_OPCODE_. + * Conceptually the bind information is a table of tuples: + * + * The opcodes are a compressed way to encode the table by only + * encoding when a column changes. In addition simple patterns + * like for runs of pointers initialzed to the same value can be + * encoded in a few bytes. + */ + uint32_t bind_off; /* file offset to binding info */ + uint32_t bind_size; /* size of binding info */ + + /* + * Some C++ programs require dyld to unique symbols so that all + * images in the process use the same copy of some code/data. + * This step is done after binding. The content of the weak_bind + * info is an opcode stream like the bind_info. But it is sorted + * alphabetically by symbol name. This enable dyld to walk + * all images with weak binding information in order and look + * for collisions. If there are no collisions, dyld does + * no updating. That means that some fixups are also encoded + * in the bind_info. For instance, all calls to "operator new" + * are first bound to libstdc++.dylib using the information + * in bind_info. Then if some image overrides operator new + * that is detected when the weak_bind information is processed + * and the call to operator new is then rebound. + */ + uint32_t weak_bind_off; /* file offset to weak binding info */ + uint32_t weak_bind_size; /* size of weak binding info */ + + /* + * Some uses of external symbols do not need to be bound immediately. + * Instead they can be lazily bound on first use. The lazy_bind + * are contains a stream of BIND opcodes to bind all lazy symbols. + * Normal use is that dyld ignores the lazy_bind section when + * loading an image. Instead the static linker arranged for the + * lazy pointer to initially point to a helper function which + * pushes the offset into the lazy_bind area for the symbol + * needing to be bound, then jumps to dyld which simply adds + * the offset to lazy_bind_off to get the information on what + * to bind. + */ + uint32_t lazy_bind_off; /* file offset to lazy binding info */ + uint32_t lazy_bind_size; /* size of lazy binding infs */ + + /* + * The symbols exported by a dylib are encoded in a trie. This + * is a compact representation that factors out common prefixes. + * It also reduces LINKEDIT pages in RAM because it encodes all + * information (name, address, flags) in one small, contiguous range. + * The export area is a stream of nodes. The first node sequentially + * is the start node for the trie. + * + * Nodes for a symbol start with a uleb128 that is the length of + * the exported symbol information for the string so far. + * If there is no exported symbol, the node starts with a zero byte. + * If there is exported info, it follows the length. + * + * First is a uleb128 containing flags. Normally, it is followed by + * a uleb128 encoded offset which is location of the content named + * by the symbol from the mach_header for the image. If the flags + * is EXPORT_SYMBOL_FLAGS_REEXPORT, then following the flags is + * a uleb128 encoded library ordinal, then a zero terminated + * UTF8 string. If the string is zero length, then the symbol + * is re-export from the specified dylib with the same name. + * If the flags is EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER, then following + * the flags is two uleb128s: the stub offset and the resolver offset. + * The stub is used by non-lazy pointers. The resolver is used + * by lazy pointers and must be called to get the actual address to use. + * + * After the optional exported symbol information is a byte of + * how many edges (0-255) that this node has leaving it, + * followed by each edge. + * Each edge is a zero terminated UTF8 of the addition chars + * in the symbol, followed by a uleb128 offset for the node that + * edge points to. + * + */ + uint32_t export_off; /* file offset to lazy binding info */ + uint32_t export_size; /* size of lazy binding infs */ +}; + +/* + * The following are used to encode rebasing information + */ +#define REBASE_TYPE_POINTER 1 +#define REBASE_TYPE_TEXT_ABSOLUTE32 2 +#define REBASE_TYPE_TEXT_PCREL32 3 + +#define REBASE_OPCODE_MASK 0xF0 +#define REBASE_IMMEDIATE_MASK 0x0F +#define REBASE_OPCODE_DONE 0x00 +#define REBASE_OPCODE_SET_TYPE_IMM 0x10 +#define REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB 0x20 +#define REBASE_OPCODE_ADD_ADDR_ULEB 0x30 +#define REBASE_OPCODE_ADD_ADDR_IMM_SCALED 0x40 +#define REBASE_OPCODE_DO_REBASE_IMM_TIMES 0x50 +#define REBASE_OPCODE_DO_REBASE_ULEB_TIMES 0x60 +#define REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB 0x70 +#define REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB 0x80 + + +/* + * The following are used to encode binding information + */ +#define BIND_TYPE_POINTER 1 +#define BIND_TYPE_TEXT_ABSOLUTE32 2 +#define BIND_TYPE_TEXT_PCREL32 3 + +#define BIND_SPECIAL_DYLIB_SELF 0 +#define BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE -1 +#define BIND_SPECIAL_DYLIB_FLAT_LOOKUP -2 +#define BIND_SPECIAL_DYLIB_WEAK_LOOKUP -3 + +#define BIND_SYMBOL_FLAGS_WEAK_IMPORT 0x1 +#define BIND_SYMBOL_FLAGS_NON_WEAK_DEFINITION 0x8 + +#define BIND_OPCODE_MASK 0xF0 +#define BIND_IMMEDIATE_MASK 0x0F +#define BIND_OPCODE_DONE 0x00 +#define BIND_OPCODE_SET_DYLIB_ORDINAL_IMM 0x10 +#define BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB 0x20 +#define BIND_OPCODE_SET_DYLIB_SPECIAL_IMM 0x30 +#define BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM 0x40 +#define BIND_OPCODE_SET_TYPE_IMM 0x50 +#define BIND_OPCODE_SET_ADDEND_SLEB 0x60 +#define BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB 0x70 +#define BIND_OPCODE_ADD_ADDR_ULEB 0x80 +#define BIND_OPCODE_DO_BIND 0x90 +#define BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB 0xA0 +#define BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED 0xB0 +#define BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB 0xC0 +#define BIND_OPCODE_THREADED 0xD0 +#define BIND_SUBOPCODE_THREADED_SET_BIND_ORDINAL_TABLE_SIZE_ULEB 0x00 +#define BIND_SUBOPCODE_THREADED_APPLY 0x01 + + +/* + * The following are used on the flags byte of a terminal node + * in the export information. + */ +#define EXPORT_SYMBOL_FLAGS_KIND_MASK 0x03 +#define EXPORT_SYMBOL_FLAGS_KIND_REGULAR 0x00 +#define EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL 0x01 +#define EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE 0x02 +#define EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION 0x04 +#define EXPORT_SYMBOL_FLAGS_REEXPORT 0x08 +#define EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER 0x10 + +/* + * The linker_option_command contains linker options embedded in object files. + */ +struct linker_option_command { + uint32_t cmd; /* LC_LINKER_OPTION only used in MH_OBJECT filetypes */ + uint32_t cmdsize; + uint32_t count; /* number of strings */ + /* concatenation of zero terminated UTF8 strings. + Zero filled at end to align */ +}; + +/* + * The symseg_command contains the offset and size of the GNU style + * symbol table information as described in the header file . + * The symbol roots of the symbol segments must also be aligned properly + * in the file. So the requirement of keeping the offsets aligned to a + * multiple of a 4 bytes translates to the length field of the symbol + * roots also being a multiple of a long. Also the padding must again be + * zeroed. (THIS IS OBSOLETE and no longer supported). + */ +struct symseg_command { + uint32_t cmd; /* LC_SYMSEG */ + uint32_t cmdsize; /* sizeof(struct symseg_command) */ + uint32_t offset; /* symbol segment offset */ + uint32_t size; /* symbol segment size in bytes */ +}; + +/* + * The ident_command contains a free format string table following the + * ident_command structure. The strings are null terminated and the size of + * the command is padded out with zero bytes to a multiple of 4 bytes/ + * (THIS IS OBSOLETE and no longer supported). + */ +struct ident_command { + uint32_t cmd; /* LC_IDENT */ + uint32_t cmdsize; /* strings that follow this command */ +}; + +/* + * The fvmfile_command contains a reference to a file to be loaded at the + * specified virtual address. (Presently, this command is reserved for + * internal use. The kernel ignores this command when loading a program into + * memory). + */ +struct fvmfile_command { + uint32_t cmd; /* LC_FVMFILE */ + uint32_t cmdsize; /* includes pathname string */ + union lc_str name; /* files pathname */ + uint32_t header_addr; /* files virtual address */ +}; + + +/* + * The entry_point_command is a replacement for thread_command. + * It is used for main executables to specify the location (file offset) + * of main(). If -stack_size was used at link time, the stacksize + * field will contain the stack size need for the main thread. + */ +struct entry_point_command { + uint32_t cmd; /* LC_MAIN only used in MH_EXECUTE filetypes */ + uint32_t cmdsize; /* 24 */ + uint64_t entryoff; /* file (__TEXT) offset of main() */ + uint64_t stacksize;/* if not zero, initial stack size */ +}; + + +/* + * The source_version_command is an optional load command containing + * the version of the sources used to build the binary. + */ +struct source_version_command { + uint32_t cmd; /* LC_SOURCE_VERSION */ + uint32_t cmdsize; /* 16 */ + uint64_t version; /* A.B.C.D.E packed as a24.b10.c10.d10.e10 */ +}; + + +/* + * The LC_DATA_IN_CODE load commands uses a linkedit_data_command + * to point to an array of data_in_code_entry entries. Each entry + * describes a range of data in a code section. + */ +struct data_in_code_entry { + uint32_t offset; /* from mach_header to start of data range*/ + uint16_t length; /* number of bytes in data range */ + uint16_t kind; /* a DICE_KIND_* value */ +}; +#define DICE_KIND_DATA 0x0001 +#define DICE_KIND_JUMP_TABLE8 0x0002 +#define DICE_KIND_JUMP_TABLE16 0x0003 +#define DICE_KIND_JUMP_TABLE32 0x0004 +#define DICE_KIND_ABS_JUMP_TABLE32 0x0005 + + + +/* + * Sections of type S_THREAD_LOCAL_VARIABLES contain an array + * of tlv_descriptor structures. + */ +struct tlv_descriptor +{ + void* (*thunk)(struct tlv_descriptor*); + unsigned long key; + unsigned long offset; +}; + +/* + * LC_NOTE commands describe a region of arbitrary data included in a Mach-O + * file. Its initial use is to record extra data in MH_CORE files. + */ +struct note_command { + uint32_t cmd; /* LC_NOTE */ + uint32_t cmdsize; /* sizeof(struct note_command) */ + char data_owner[16]; /* owner name for this LC_NOTE */ + uint64_t offset; /* file offset of this data */ + uint64_t size; /* length of data region */ +}; + +#endif /* _MACHO_LOADER_H_ */ diff --git a/third_party/xnu/README.crashpad b/third_party/xnu/README.crashpad new file mode 100644 index 00000000..f8471d38 --- /dev/null +++ b/third_party/xnu/README.crashpad @@ -0,0 +1,25 @@ +Name: XNU +Short Name: xnu +URL: https://opensource.apple.com/source/xnu/ +URL: https://opensource.apple.com/tarballs/xnu/ +Version: 6153.11.26 (from macOS 10.15.0) +License: APSL 2.0 +License File: APPLE_LICENSE +Security Critical: no + +Description: +XNU is the operating system kernel used on macOS and other Apple systems. + +Local Modifications: + - EXTERNAL_HEADERS/mach-o/loader.h is present. Its #includes of + and have been + commented out as unnecessary. Note that its #includes of and + have been retained but these headers have not been provided. + External headers must be made available to provide the cpu_type_t, + cpu_subtype_t, and vm_prot_t types. + - osfmk/mach/exc.defs and osfmk/mach/mach_exc.defs are present, to fill in + for and on iOS, where they are missing. + The .defs files they depend on, , + , and are also + included. + - Anything not listed above is omitted. diff --git a/third_party/xnu/osfmk/mach/exc.defs b/third_party/xnu/osfmk/mach/exc.defs new file mode 100644 index 00000000..734e7408 --- /dev/null +++ b/third_party/xnu/osfmk/mach/exc.defs @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2000 Apple Computer, Inc. All rights reserved. + * + * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. The rights granted to you under the License + * may not be used to create, or enable the creation or redistribution of, + * unlawful or unlicensed copies of an Apple operating system, or to + * circumvent, violate, or enable the circumvention or violation of, any + * terms of an Apple operating system software license agreement. + * + * Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ + */ +/* + * @OSF_COPYRIGHT@ + */ +/* + * Mach Operating System + * Copyright (c) 1991,1990,1989,1988,1987 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie Mellon + * the rights to redistribute these changes. + */ +/* + */ +/* + * Abstract: + * MiG definitions file for Mach exception interface. + */ + +subsystem +#if KERNEL_SERVER + KernelServer +#endif /* KERNEL_SERVER */ + +#if KERNEL_USER + KernelUser +#endif + exc 2401; + +#include +#include + +ServerPrefix catch_; + +type exception_data_t = array[*:2] of integer_t; +type exception_type_t = int; + +routine exception_raise( + exception_port : mach_port_t; + thread : mach_port_t; + task : mach_port_t; + exception : exception_type_t; + code : exception_data_t +#if EXC_SERVER_SECTOKEN + ; + ServerSecToken stoken : security_token_t +#endif +#if EXC_SERVER_AUDITTOKEN + ; + ServerAuditToken atoken: audit_token_t +#endif + ); + +routine exception_raise_state( + exception_port : mach_port_t; + exception : exception_type_t; + code : exception_data_t, const; + inout flavor : int; + old_state : thread_state_t, const; + out new_state : thread_state_t +#if EXC_SERVER_SECTOKEN + ; + ServerSecToken stoken : security_token_t +#endif +#if EXC_SERVER_AUDITTOKEN + ; + ServerAuditToken atoken: audit_token_t +#endif + ); + +routine exception_raise_state_identity( + exception_port : mach_port_t; + thread : mach_port_t; + task : mach_port_t; + exception : exception_type_t; + code : exception_data_t; + inout flavor : int; + old_state : thread_state_t; + out new_state : thread_state_t +#if EXC_SERVER_SECTOKEN + ; + ServerSecToken stoken : security_token_t +#endif +#if EXC_SERVER_AUDITTOKEN + ; + ServerAuditToken atoken: audit_token_t +#endif + ); + +/* vim: set ft=c : */ diff --git a/third_party/xnu/osfmk/mach/mach_exc.defs b/third_party/xnu/osfmk/mach/mach_exc.defs new file mode 100644 index 00000000..5ce6427b --- /dev/null +++ b/third_party/xnu/osfmk/mach/mach_exc.defs @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2006 Apple Computer, Inc. All rights reserved. + * + * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. The rights granted to you under the License + * may not be used to create, or enable the creation or redistribution of, + * unlawful or unlicensed copies of an Apple operating system, or to + * circumvent, violate, or enable the circumvention or violation of, any + * terms of an Apple operating system software license agreement. + * + * Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ + */ +/* + * @OSF_COPYRIGHT@ + */ +/* + * Mach Operating System + * Copyright (c) 1991,1990,1989,1988,1987 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie Mellon + * the rights to redistribute these changes. + */ +/* + */ +/* + * Abstract: + * MiG definitions file for Mach exception interface. + */ + +subsystem +#if KERNEL_USER + KernelUser +#endif +#if KERNEL_SERVER + KernelServer +#endif /* KERNEL_SERVER */ + + mach_exc 2405; + +#include +#include + +ServerPrefix catch_; + +type mach_exception_data_t = array[*:2] of int64_t; +type exception_type_t = int; + +routine mach_exception_raise( + exception_port : mach_port_t; + thread : mach_port_t; + task : mach_port_t; + exception : exception_type_t; + code : mach_exception_data_t +#if MACH_EXC_SERVER_SECTOKEN + ; + ServerSecToken stoken : security_token_t +#endif +#if MACH_EXC_SERVER_AUDITTOKEN + ; + ServerAuditToken atoken: audit_token_t +#endif + ); + +routine mach_exception_raise_state( + exception_port : mach_port_t; + exception : exception_type_t; + code : mach_exception_data_t, const; + inout flavor : int; + old_state : thread_state_t, const; + out new_state : thread_state_t +#if MACH_EXC_SERVER_SECTOKEN + ; + ServerSecToken stoken : security_token_t +#endif +#if MACH_EXC_SERVER_AUDITTOKEN + ; + ServerAuditToken atoken: audit_token_t +#endif + ); + +routine mach_exception_raise_state_identity( + exception_port : mach_port_t; + thread : mach_port_t; + task : mach_port_t; + exception : exception_type_t; + code : mach_exception_data_t; + inout flavor : int; + old_state : thread_state_t; + out new_state : thread_state_t +#if MACH_EXC_SERVER_SECTOKEN + ; + ServerSecToken stoken : security_token_t +#endif +#if MACH_EXC_SERVER_AUDITTOKEN + ; + ServerAuditToken atoken: audit_token_t +#endif + ); + +/* vim: set ft=c : */ diff --git a/third_party/xnu/osfmk/mach/mach_types.defs b/third_party/xnu/osfmk/mach/mach_types.defs new file mode 100644 index 00000000..d2e9fb0b --- /dev/null +++ b/third_party/xnu/osfmk/mach/mach_types.defs @@ -0,0 +1,652 @@ +/* + * Copyright (c) 2000-2016 Apple Inc. All rights reserved. + * + * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. The rights granted to you under the License + * may not be used to create, or enable the creation or redistribution of, + * unlawful or unlicensed copies of an Apple operating system, or to + * circumvent, violate, or enable the circumvention or violation of, any + * terms of an Apple operating system software license agreement. + * + * Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ + */ +/* + * @OSF_COPYRIGHT@ + */ +/* + * Mach Operating System + * Copyright (c) 1991,1990,1989,1988 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie Mellon + * the rights to redistribute these changes. + */ +/* + * NOTICE: This file was modified by McAfee Research in 2004 to introduce + * support for mandatory and extensible security protections. This notice + * is included in support of clause 2.2 (b) of the Apple Public License, + * Version 2.0. + */ +/* + */ +/* + * Mach kernel interface type declarations + */ + +#ifndef _MACH_MACH_TYPES_DEFS_ +#define _MACH_MACH_TYPES_DEFS_ + + +#include + +type memory_object_offset_t = uint64_t; +type memory_object_size_t = uint64_t; +type memory_object_cluster_size_t = uint32_t; +type memory_object_fault_info_t = array[16] of integer_t; + +#ifdef KERNEL_PRIVATE + +/* Universal Page Lists - restricted to (in-kernel) pagers for now */ +type upl_size_t = uint32_t; +type upl_offset_t = uint32_t; +type upl_page_info_t = struct[2] of integer_t; +type upl_page_info_array_t = array[*:256] of upl_page_info_t; + +type upl_t = mach_port_t + intran: upl_t convert_port_to_upl(mach_port_t) + outtran: mach_port_t convert_upl_to_port(upl_t) + destructor: upl_deallocate(upl_t) + ; + +#endif /* KERNEL_PRIVATE */ + +type mach_port_status_t = struct[10] of integer_t; /* obsolete */ +type mach_port_info_ext_t = struct[17] of integer_t; + + /* mach_port_info_t: can hold either a + * mach_port_status_t (9 ints) or a + * mach_port_limits_t (1 int) or a + * mach_port_info_ext_t (17 ints). If new flavors of + * mach_port_{get,set}_attributes are added, the size of + * this array may have to be increased. (See mach/port.h) + */ +type mach_port_flavor_t = int; +type mach_port_info_t = array[*:17] of integer_t; + + /* + * mach_msg_max_trailer_t: can hold + * mach_msg_trailer_type_t (1 int) + * mach_msg_trailer_size_t (1 int) + * mach_port_seqno_t (1 int) + * security_token_t (2 ints) + * audit_token_t (8 ints) + * mach_port_context_t (2 ints) + * msgh_ad (1 int) + * msg_labels_t (1 int) + */ +type mach_msg_trailer_type_t = int; +type mach_msg_trailer_info_t = array[*:68] of char; + +type task_t = mach_port_t +#if KERNEL_SERVER + intran: task_t convert_port_to_task(mach_port_t) + outtran: mach_port_t convert_task_to_port(task_t) + destructor: task_deallocate(task_t) +#endif /* KERNEL_SERVER */ + ; + +type task_name_t = mach_port_t +#if KERNEL_SERVER + intran: task_name_t convert_port_to_task_name(mach_port_t) + outtran: mach_port_t convert_task_name_to_port(task_name_t) + destructor: task_name_deallocate(task_name_t) +#endif /* KERNEL_SERVER */ + ; + +type task_inspect_t = mach_port_t +#if KERNEL_SERVER + intran: task_inspect_t convert_port_to_task_inspect(mach_port_t) + outtran: mach_port_t convert_task_inspect_to_port(task_inspect_t) + destructor: task_inspect_deallocate(task_inspect_t) +#endif /* KERNEL_SERVER */ + ; + +type thread_t = mach_port_t +#if KERNEL_SERVER + intran: thread_t convert_port_to_thread(mach_port_t) + outtran: mach_port_t convert_thread_to_port(thread_t) + destructor: thread_deallocate(thread_t) +#endif /* KERNEL_SERVER */ + ; + +type thread_inspect_t = mach_port_t +#if KERNEL_SERVER + intran: thread_inspect_t convert_port_to_thread_inspect(mach_port_t) + outtran: mach_port_t convert_thread_inspect_to_port(thread_inspect_t) + destructor: thread_inspect_deallocate(thread_inspect_t) +#endif /* KERNEL_SERVER */ + ; + +type thread_act_t = mach_port_t +#if KERNEL_SERVER + intran: thread_act_t convert_port_to_thread(mach_port_t) + outtran: mach_port_t convert_thread_to_port(thread_act_t) + destructor: thread_deallocate(thread_act_t) +#endif /* KERNEL_SERVER */ + ; + +type thread_act_consume_ref_t = mach_port_move_send_t + cusertype: thread_act_t +#if KERNEL_SERVER + intran: thread_act_t convert_port_to_thread(mach_port_t) + destructor: thread_deallocate(thread_act_t) +#endif /* KERNEL_SERVER */ + ; + + /* thread_state_t: This inline array can hold + * a machine-dependent amount of data, defined in + * mach/machine/???? (currently THREAD_STATE_MAX, + * in mach/thread_state.h) + */ +#include +type thread_state_flavor_t = int; +type thread_state_t = array[*:THREAD_STATE_MAX] of natural_t; + +type task_array_t = ^array[] of task_t; +type thread_array_t = ^array[] of thread_t; +type thread_act_array_t = ^array[] of thread_act_t; +type act_params_t = array[6] of int; + +type vm_map_t = mach_port_t +#if KERNEL_SERVER + intran: vm_map_t convert_port_to_map(mach_port_t) + destructor: vm_map_deallocate(vm_map_t) +#endif /* KERNEL_SERVER */ + ; + +type vm_task_entry_t = mach_port_t + cusertype: vm_map_t +#if KERNEL_SERVER + intran: vm_map_t convert_port_entry_to_map(mach_port_t) + destructor: vm_map_deallocate(vm_map_t) +#endif /* KERNEL_SERVER */ + ; + +type ipc_space_t = mach_port_t +#if KERNEL_SERVER + intran: ipc_space_t convert_port_to_space(mach_port_t) + destructor: space_deallocate(ipc_space_t) +#endif /* KERNEL_SERVER */ + ; + +type ipc_space_inspect_t = mach_port_t +#if KERNEL_SERVER + intran: ipc_space_inspect_t convert_port_to_space_inspect(mach_port_t) + destructor: space_inspect_deallocate(ipc_space_inspect_t) +#endif /* KERNEL_SERVER */ + ; + +type arcade_register_t = mach_port_t +#if KERNEL_SERVER + intran: arcade_register_t convert_port_to_arcade_register(mach_port_t) +#endif /* KERNEL_SERVER */ + ; + +type vm_prot_t = int; +type vm_inherit_t = int; +type vm_purgable_t = int; +type xxx_vm_statistics_data_t = struct[13] of integer_t; +type vm_behavior_t = int; +type vm_statistics_data_t = struct[15] of integer_t; +type vm_machine_attribute_t = int; +type vm_machine_attribute_val_t = int; +type vm_sync_t = int; + + /* thread_info_t: this inline array can hold any of: + * thread_basic_info_t (10 ints) + * policy_timeshare_info_t (5 ints) + * policy_fifo_info_t (4 ints) + * policy_rr_info_t (5 ints) + * thread_extended_info (12 ints + 64 chars) + * if other thread_info flavors are added, this + * definition may need to be changed. (See + * mach/thread_info.h and mach/policy.h) */ +type thread_flavor_t = int; +type thread_info_t = array[*:32] of integer_t; + +type thread_policy_flavor_t = natural_t; +type thread_policy_t = array[*:16] of integer_t; + + /* task_info_t: this inline array can hold any of: + * task_basic_info_32_t (8 ints) + * task_basic_info_64_t (10 ints) + * task_events_info_t (8 ints) + * task_thread_times_info_t (4 ints) + * policy_timeshare_info_t (5 ints) + * policy_fifo_info_t (4 ints) + * policy_rr_info_t (5 ints) + * task security token (2 ints) + * task audit token (8 ints) + * dyld info (2 64-bit ints and 1 int) + * task_extmod_info_t (8 64-bit ints) + * task_basic_info_64_2_t + * mach_task_basic_info_t (12 ints) + * task_power_info_t (18 ints) + * task_vm_info_t (87 ints) + * If other task_info flavors are added, this + * definition may need to be changed. (See + * mach/task_info.h and mach/policy.h) */ +type task_flavor_t = int; +type task_info_t = array[*:87] of integer_t; + +type task_purgable_info_t = struct[68] of integer_t; + +type task_policy_flavor_t = natural_t; +type task_policy_t = array[*:16] of integer_t; + +type task_inspect_flavor_t = natural_t; +type task_inspect_info_t = array[*:4] of integer_t; + +type task_exc_guard_behavior_t = uint32_t; + +type mem_entry_name_port_t = mach_port_t +#if KERNEL_SERVER + intran: mem_entry_name_port_t null_conversion(mach_port_t) + outtran: mach_port_t null_conversion(mem_entry_name_port_t) +#endif /* KERNEL_SERVER */ + ; + +type mem_entry_name_port_move_send_t = mach_port_move_send_t + cusertype: mem_entry_name_port_t +#if KERNEL_SERVER + intran: mem_entry_name_port_t null_conversion(mach_port_t) + outtran: mach_port_t null_conversion(mem_entry_name_port_t) +#endif /* KERNEL_SERVER */ + ; + +type memory_object_default_t = mach_port_t +#if KERNEL_PRIVATE + intran: memory_object_default_t null_conversion(mach_port_t) + outtran: mach_port_t null_conversion(memory_object_default_t) +#endif /* KERNEL_PRIVATE */ + ; + +type memory_object_t = mach_port_t +#if KERNEL_PRIVATE + intran: memory_object_t convert_port_to_memory_object(mach_port_t) + outtran: mach_port_t convert_memory_object_to_port(memory_object_t) +#endif /* KERNEL_PRIVATE */ + ; + + +type memory_object_control_t = mach_port_t +#if KERNEL_PRIVATE + intran: memory_object_control_t convert_port_to_mo_control(mach_port_t) + outtran: mach_port_t convert_mo_control_to_port(memory_object_control_t) + destructor: memory_object_control_deallocate(memory_object_control_t) +#endif /* KERNEL_PRIVATE */ + ; + +type memory_object_name_t = mach_port_t + ctype: mach_port_t + ; + + +type memory_object_copy_strategy_t = int; +type memory_object_return_t = int; + +type machine_info_data_t = struct[5] of integer_t; +type machine_slot_data_t = struct[8] of integer_t; + +type host_t = mach_port_t +#if KERNEL_SERVER + intran: host_t convert_port_to_host(mach_port_t) + outtran: mach_port_t convert_host_to_port(host_t) +#endif /* KERNEL_SERVER */ + ; + +type host_priv_t = mach_port_t +#if KERNEL_SERVER + intran: host_priv_t convert_port_to_host_priv(mach_port_t) +#endif /* KERNEL_SERVER */ + ; + +type host_security_t = mach_port_t +#if KERNEL_SERVER + intran: host_security_t convert_port_to_host_security(mach_port_t) +#endif /* KERNEL_SERVER */ + ; + + /* + * host_info_t: variable-sized inline array that can contain: + * + * host_basic_info_old_t (5 ints) + * host_basic_info_t (12 ints) + * host_sched_info_t (2 ints) + * kernel_resource_sizes_t (5 ints) + * host_load_info_t (6 ints) + * vm_statistics32_t (15 ints) + * host_purgable_info_t (68 ints) + * host_expired_task_info uses a task_power_info (18 ints) + * + * If other host_info flavors are added, this definition may + * need to be changed. (See mach/{host_info,vm_statistics}.h) + */ +type host_flavor_t = int; +type host_info_t = array[*:68] of integer_t; + /* + * host_info64_t: variable-sized inline array that can contain: + * + * vm_statistics_t (6 ints and 9 longs) + * vm_extmod_statistics_t (6 64-bit ints) + */ +type host_info64_t = array[*:256] of integer_t; + +type processor_t = mach_port_t +#if KERNEL_SERVER + intran: processor_t convert_port_to_processor(mach_port_t) + outtran: mach_port_t convert_processor_to_port(processor_t) +#endif /* KERNEL_SERVER */ + ; + +type processor_array_t = ^array[] of processor_t; + + /* + * processor_info_t: variable-sized inline array that can + * contain: + * + * - processor_basic_info_t: (5 ints) + * - processor_cpu_load_info_t: (4 ints) + * - processor_machine_info_t: (12 ints) + * - processor_cpu_stat_t: (10 ints) + * - processor_cpu_stat64_t: (20 ints) + * + * If other processor_info flavors are added, this definition + * may need to be changed. + * + * See mach/processor_info.h and mach/arm/processor_info.h. + */ + +type processor_flavor_t = int; +type processor_info_t = array[*:20] of integer_t; +type processor_info_array_t = ^array[] of integer_t; + +type processor_set_t = mach_port_t +#if KERNEL_SERVER + intran: processor_set_t convert_port_to_pset(mach_port_t) + outtran: mach_port_t convert_pset_to_port(processor_set_t) + destructor: pset_deallocate(processor_set_t) +#endif /* KERNEL_SERVER */ + ; + +type processor_set_array_t = ^array[] of processor_set_t; + +type processor_set_name_t = mach_port_t +#if KERNEL_SERVER + intran: processor_set_name_t convert_port_to_pset_name(mach_port_t) + outtran: mach_port_t convert_pset_name_to_port(processor_set_name_t) + destructor: pset_deallocate(processor_set_name_t) +#endif /* KERNEL_SERVER */ + ; + +type processor_set_name_array_t = ^array[] of processor_set_name_t; + + /* processor_set_info_t: variable-size inline array + * that can hold: + * processor_set_basic_info (5 ints) + * processor_set_load_info (4 ints) + * policy_timeshare_base_t (1 int) + * policy_fifo_base_t (1 int) + * policy_rr_base_t (1 int) + * policy_timeshare_base_t (1 int) + * policy_fifo_base_t (1 int) + * policy_rr_base_t (1 int) + * policy_t (1 int) + * If other flavors are added, this definition may + * need to be changed. (see mach/processor.h) */ +type processor_set_flavor_t = int; +type processor_set_info_t = array[*:5] of integer_t; + +type bootstrap_t = mach_port_t; + +type kernel_version_t = c_string[*:512]; +type kernel_boot_info_t = c_string[*:4096]; + +type time_value_t = struct[2] of integer_t; + +type mach_port_qos_t = struct[2] of integer_t; + +type mach_port_options_t = struct[3] of uint64_t; +type mach_port_options_ptr_t = ^ mach_port_options_t; + +type emulation_vector_t = ^array[] of vm_offset_t; + +type inline_existence_map_t = array[*:512] of char; + +type policy_t = int; + /* policy_info_t: variable-size inline array. Can hold: + * policy_timeshare_info_t (5 ints) + * policy_fifo_info_t (4 ints) + * policy_rr_info_t (5 ints) */ +type policy_base_t = array[*:5] of integer_t; +type policy_info_t = array[*:2] of integer_t; +type policy_limit_t = array[*:1] of integer_t; + +type ledger_t = mach_port_t +#if KERNEL_SERVER + intran: ledger_t convert_port_to_ledger(mach_port_t) + outtran: mach_port_t convert_ledger_to_port(ledger_t) +#endif /* KERNEL_SERVER */ + ; + +type ledger_array_t = ^array[] of ledger_t; +type ledger_item_t = integer_t; + /* DEPRECATED */ + +type ledger_amount_t = int64_t; + +type security_token_t = struct[2] of uint32_t; +type audit_token_t = struct[8] of uint32_t; + +type msg_labels_t = mach_port_t; + + /* memory_object_info_t: variable-size inline array: + * memory_object_attr_info_t (5 ints) + * XXX actually it's 6 ints temporarily (object_ready!) + * memory_object_behave_info_t (4 ints) + * memory_object_perf_info_t (2 ints) + * old_memory_object_attr_info_t (3 ints) + * If other flavors are added, this definition may + * need to be changed. (see mach/memory_object.h) */ +type memory_object_flavor_t = int; +type memory_object_info_t = array[*:6] of int; + + /* vm_region_info_t: variable-size inline array that can hold: + * vm_region_basic_info_t (8 ints) + * If other flavors are added, this definition may + * need to be changed. (see mach/vm_region.h) */ +type vm_region_flavor_t = int; +type vm_region_info_t = array[*:10] of int; +type vm_region_recurse_info_t = array[*:19] of int; + +type vm_page_info_flavor_t = int; +type vm_page_info_t = array[*:32] of int; + +type mach_vm_read_entry_t = array[512] of mach_vm_offset_t; +type vm_read_entry_t = array[512] of vm_offset_t; +#ifdef VM32_SUPPORT +type vm32_read_entry_t = array[512] of vm32_offset_t; +#endif + +type exception_mask_t = int; +type exception_behavior_t = int; + +type exception_handler_t = mach_port_t; + +type exception_handler_array_t = + array[*:32] of exception_handler_t; + +type exception_behavior_array_t = + array[*:32] of exception_behavior_t; + +type exception_flavor_array_t = + array[*:32] of thread_state_flavor_t; + +type exception_mask_array_t = + array[*:32] of exception_mask_t; + +type semaphore_t = mach_port_t +#if KERNEL_SERVER + intran: semaphore_t convert_port_to_semaphore(mach_port_t) + outtran: mach_port_t convert_semaphore_to_port(semaphore_t) + destructor: semaphore_dereference(semaphore_t) +#endif /* KERNEL_SERVER */ + ; + +type semaphore_consume_ref_t = mach_port_move_send_t + cusertype: semaphore_t +#if KERNEL_SERVER + intran: semaphore_t convert_port_to_semaphore(mach_port_t) + outtran: mach_port_t convert_semaphore_to_port(semaphore_t) +#endif /* KERNEL_SERVER */ + ; + +type lock_set_t = mach_port_t +#if KERNEL_SERVER + intran: lock_set_t convert_port_to_lock_set(mach_port_t) + outtran: mach_port_t convert_lock_set_to_port(lock_set_t) + destructor: lock_set_dereference(lock_set_t) +#endif /* KERNEL_SERVER */ + ; + +type task_suspension_token_t = mach_port_move_send_once_t +#if KERNEL_SERVER + intran: task_suspension_token_t convert_port_to_task_suspension_token(mach_port_t) + outtran: mach_port_t convert_task_suspension_token_to_port(task_suspension_token_t) +#endif /* KERNEL_SERVER */ + ; + +type vfs_path_t = c_string[4096]; +type nspace_path_t = c_string[1024]; /* 1024 == PATH_MAX */ + +/* public voucher types */ + +/* Mach voucher object */ +type mach_voucher_t = mach_port_t; +type mach_voucher_name_t = mach_port_name_t; + +type mach_voucher_attr_manager_t = mach_port_t; +type mach_voucher_attr_control_t = mach_port_t; + +/* IPC voucher internal object */ +type ipc_voucher_t = mach_port_t +#if KERNEL_SERVER + intran: ipc_voucher_t convert_port_to_voucher(mach_port_t) + outtran: mach_port_t convert_voucher_to_port(ipc_voucher_t) + destructor: ipc_voucher_release(ipc_voucher_t) +#endif /* KERNEL_SERVER */ + ; + +/* IPC voucher attribute control internal object */ +type ipc_voucher_attr_control_t = mach_port_t +#if KERNEL_SERVER + intran: ipc_voucher_attr_control_t convert_port_to_voucher_attr_control(mach_port_t) + outtran: mach_port_t convert_voucher_attr_control_to_port(ipc_voucher_attr_control_t) + destructor: ipc_voucher_attr_control_release(ipc_voucher_attr_control_t) +#endif /* KERNEL_SERVER */ + ; + +type mach_voucher_attr_key_t = uint32_t; + +type mach_voucher_attr_command_t = uint32_t; +type mach_voucher_attr_recipe_command_t = uint32_t; + +type mach_voucher_attr_content_size_t = uint32_t; +type mach_voucher_attr_content_t = array[*:4096] of uint8_t; +type mach_voucher_attr_content_array_t = array[*:5120] of uint8_t; + +type mach_voucher_attr_raw_recipe_size_t = uint32_t; +type mach_voucher_attr_raw_recipe_t = array[*:4096] of uint8_t; +type mach_voucher_attr_raw_recipe_array_t = array[*:5120] of uint8_t; + +type mach_voucher_selector_t = uint32_t; + +type mach_voucher_attr_value_handle_t = uint64_t; +type mach_voucher_attr_value_handle_array_t = array[*:4] of mach_voucher_attr_value_handle_t; +type mach_voucher_attr_value_reference_t = uint32_t; + +/* kernel module loader */ +type kmod_t = int; +type kmod_control_flavor_t = int; + +type kmod_args_t = ^array[] of MACH_MSG_TYPE_BYTE + ctype: kmod_args_t; + +type io_master_t = mach_port_t; +type UNDServerRef = mach_port_t; + +/* These must be kept in sync with definitions in osfmk/mach/dyld_kernel.h */ +type dyld_kernel_image_info_t = struct[40] of MACH_MSG_TYPE_BYTE; +type dyld_kernel_image_info_array_t = ^array[] of dyld_kernel_image_info_t; +type dyld_kernel_process_info_t = struct[64] of MACH_MSG_TYPE_BYTE; + +#if KERNEL_SERVER +#ifdef MACH_KERNEL_PRIVATE +simport ; /* for voucher conversions */ +simport ; /* for null conversion */ +simport ; /* for task/thread conversion */ +simport ; /* for host/processor/pset conversions */ +simport ; /* for lock_set and semaphore conversions */ +simport ; /* for ledger conversions */ +simport ; /* for processor conversions */ +simport ; /* for lock-set conversions */ +simport ; /* for semaphore conversions */ +simport ; /* for memory object type conversions */ +simport ; /* for vm_map conversions */ +#if CONFIG_ARCADE +simport ; /* for arcade_register conversions */ +#endif +#endif /* MACH_KERNEL_PRIVATE */ + +simport ; /* pick up kernel-specific MIG things */ + +#endif /* KERNEL_SERVER */ + +import ; +import ; + +#endif /* _MACH_MACH_TYPES_DEFS_ */ + +/* vim: set ft=c : */ diff --git a/third_party/xnu/osfmk/mach/machine/machine_types.defs b/third_party/xnu/osfmk/mach/machine/machine_types.defs new file mode 100644 index 00000000..f4813948 --- /dev/null +++ b/third_party/xnu/osfmk/mach/machine/machine_types.defs @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2000-2007 Apple Computer, Inc. All rights reserved. + * + * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. The rights granted to you under the License + * may not be used to create, or enable the creation or redistribution of, + * unlawful or unlicensed copies of an Apple operating system, or to + * circumvent, violate, or enable the circumvention or violation of, any + * terms of an Apple operating system software license agreement. + * + * Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ + */ +/* + * @OSF_COPYRIGHT@ + */ + +/* + * Header file for basic, machine-dependent data types. arm+i386 version. + */ + +#ifndef _MACH_MACHINE_MACHNINE_TYPES_DEFS +#define _MACH_MACHINE_MACHNINE_TYPES_DEFS + +type short = int16_t; +type int = int32_t; +type unsigned = uint32_t; + +type float = MACH_MSG_TYPE_REAL_32; +type double = MACH_MSG_TYPE_REAL_64; + + +/* from ISO/IEC 988:1999 spec */ +/* 7.18.1.4 Integer types capable of hgolding object pointers */ +/* + * The [u]intptr_t types for the native + * integer type, e.g. 32 or 64 or.. whatever + * register size the machine has. They are + * used for entities that might be either + * [unsigned] integers or pointers, and for + * type-casting between the two. + * + * For instance, the IPC system represents + * a port in user space as an integer and + * in kernel space as a pointer. + */ +#if defined(__LP64__) +type uintptr_t = uint64_t; +type intptr_t = int64_t; +#else +type uintptr_t = uint32_t; +type intptr_t = int32_t; +#endif + +/* + * These are the legacy Mach types that are + * the [rough] equivalents of the standards above. + * They were defined in terms of int, not + * long int, so they remain separate. + */ +#if defined(__LP64__) +type register_t = int64_t; +#else +type register_t = int32_t; +#endif +type integer_t = int32_t; +type natural_t = uint32_t; + +/* + * These are the VM types that scale with the address + * space size of a given process. + */ + +#if defined(__LP64__) +type vm_address_t = uint64_t; +type vm_offset_t = uint64_t; +type vm_size_t = uint64_t; +#else +type vm_address_t = natural_t; +type vm_offset_t = natural_t; +type vm_size_t = natural_t; +#endif + +/* This is a bit of a hack for arm. We implement the backend with a wide type, but present a native-sized type to callers */ +type mach_port_context_t = uint64_t; + +/* + * The mach_vm_xxx_t types are sized to hold the + * maximum pointer, offset, etc... supported on the + * platform. + */ +type mach_vm_address_t = uint64_t; +type mach_vm_offset_t = uint64_t; +type mach_vm_size_t = uint64_t; + +#if MACH_IPC_COMPAT +/* + * For the old IPC interface + */ +#define MSG_TYPE_PORT_NAME natural_t + +#endif /* MACH_IPC_COMPAT */ + +/* + * These are types used internal to Mach to implement the + * legacy 32-bit VM APIs published by the kernel. + */ +#define VM32_SUPPORT 1 + +type vm32_address_t = uint32_t; +type vm32_offset_t = uint32_t; +type vm32_size_t = uint32_t; + +#endif /* _MACH_MACHINE_MACHNINE_TYPES_DEFS */ + +/* vim: set ft=c : */ diff --git a/third_party/xnu/osfmk/mach/std_types.defs b/third_party/xnu/osfmk/mach/std_types.defs new file mode 100644 index 00000000..0b483836 --- /dev/null +++ b/third_party/xnu/osfmk/mach/std_types.defs @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2002,2000 Apple Computer, Inc. All rights reserved. + * + * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. The rights granted to you under the License + * may not be used to create, or enable the creation or redistribution of, + * unlawful or unlicensed copies of an Apple operating system, or to + * circumvent, violate, or enable the circumvention or violation of, any + * terms of an Apple operating system software license agreement. + * + * Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ + */ +/* + * @OSF_COPYRIGHT@ + */ +/* + * Mach Operating System + * Copyright (c) 1991,1990,1989,1988 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie Mellon + * the rights to redistribute these changes. + */ +/* + */ +/* + * Mach kernel standard interface type declarations + */ + +#ifndef _MACH_STD_TYPES_DEFS_ +#define _MACH_STD_TYPES_DEFS_ + +/* from ISO/IEC 988:1999 spec */ +/* 7.18.1.1 Exact-width integer types */ + +type int8_t = MACH_MSG_TYPE_INTEGER_8; +type uint8_t = MACH_MSG_TYPE_INTEGER_8; +type int16_t = MACH_MSG_TYPE_INTEGER_16; +type uint16_t = MACH_MSG_TYPE_INTEGER_16; +type int32_t = MACH_MSG_TYPE_INTEGER_32; +type uint32_t = MACH_MSG_TYPE_INTEGER_32; +type int64_t = MACH_MSG_TYPE_INTEGER_64; +type uint64_t = MACH_MSG_TYPE_INTEGER_64; + +/* + * Legacy fixed-length Mach types which should + * be replaced with the Standard types from above. + */ +type int32 = int32_t; +type unsigned32 = uint32_t; +type int64 = int64_t; +type unsigned64 = uint64_t; + +/* + * Other fixed length Mach types. + */ +type char = MACH_MSG_TYPE_CHAR; +type boolean_t = MACH_MSG_TYPE_BOOLEAN; + +#include + +type kern_return_t = int; + +type pointer_t = ^array[] of MACH_MSG_TYPE_BYTE + ctype: vm_offset_t; + + +type mach_port_t = MACH_MSG_TYPE_COPY_SEND; +type mach_port_array_t = array[] of mach_port_t; + +type mach_port_name_t = MACH_MSG_TYPE_PORT_NAME; +type mach_port_name_array_t = array[] of mach_port_name_t; + +type mach_port_right_t = natural_t; + +type mach_port_type_t = natural_t; +type mach_port_type_array_t = array[] of mach_port_type_t; + +type mach_port_urefs_t = natural_t; +type mach_port_delta_t = integer_t; +type mach_port_seqno_t = natural_t; +type mach_port_mscount_t = unsigned; +type mach_port_msgcount_t = unsigned; +type mach_port_rights_t = unsigned; +type mach_msg_id_t = integer_t; +type mach_msg_size_t = natural_t; +type mach_msg_type_name_t = unsigned; +type mach_msg_options_t = integer_t; + +type mach_port_move_receive_t = MACH_MSG_TYPE_MOVE_RECEIVE + ctype: mach_port_t; +type mach_port_copy_send_t = MACH_MSG_TYPE_COPY_SEND + ctype: mach_port_t; +type mach_port_make_send_t = MACH_MSG_TYPE_MAKE_SEND + ctype: mach_port_t; +type mach_port_move_send_t = MACH_MSG_TYPE_MOVE_SEND + ctype: mach_port_t; +type mach_port_make_send_once_t = MACH_MSG_TYPE_MAKE_SEND_ONCE + ctype: mach_port_t; +type mach_port_move_send_once_t = MACH_MSG_TYPE_MOVE_SEND_ONCE + ctype: mach_port_t; + +type mach_port_receive_t = MACH_MSG_TYPE_PORT_RECEIVE + ctype: mach_port_t; +type mach_port_send_t = MACH_MSG_TYPE_PORT_SEND + ctype: mach_port_t; +type mach_port_send_once_t = MACH_MSG_TYPE_PORT_SEND_ONCE + ctype: mach_port_t; + +type mach_port_poly_t = polymorphic + ctype: mach_port_t; + +import ; +import ; + +#endif /* _MACH_STD_TYPES_DEFS_ */ + +/* vim: set ft=c : */ diff --git a/third_party/zlib/BUILD.gn b/third_party/zlib/BUILD.gn index 0723ba3e..e5a2ad38 100644 --- a/third_party/zlib/BUILD.gn +++ b/third_party/zlib/BUILD.gn @@ -14,7 +14,7 @@ import("../../build/crashpad_buildconfig.gni") -if (crashpad_is_in_chromium || crashpad_is_in_fuchsia) { +if (crashpad_is_in_chromium || crashpad_is_in_fuchsia || crashpad_is_in_dart) { zlib_source = "external" } else if (!crashpad_is_win && !crashpad_is_fuchsia) { zlib_source = "system" @@ -36,9 +36,7 @@ config("zlib_config") { if (zlib_source == "external") { group("zlib") { public_configs = [ ":zlib_config" ] - public_deps = [ - "//third_party/zlib", - ] + public_deps = [ "//third_party/zlib" ] } } else if (zlib_source == "system") { source_set("zlib") { @@ -73,9 +71,9 @@ if (zlib_source == "external") { "zlib/uncompr.c", "zlib/zconf.h", "zlib/zlib.h", - "zlib/zlib_crashpad.h", "zlib/zutil.c", "zlib/zutil.h", + "zlib_crashpad.h", ] cflags = [] @@ -91,6 +89,12 @@ if (zlib_source == "external") { "/wd4324", # structure was padded due to alignment specifier "/wd4702", # unreachable code ] + if (current_cpu == "arm64" && !crashpad_is_clang) { + # Select code path for clang in zlib to avoid using MSVC x86/x64 + # intrinsics for Windows ARM64. + # TODO: https://crashpad.chromium.org/bug/267 + defines += [ "__clang__" ] + } } else { defines += [ "HAVE_HIDDEN", @@ -98,6 +102,10 @@ if (zlib_source == "external") { ] } + configs -= [ + "//third_party/mini_chromium/mini_chromium/build:Wimplicit_fallthrough", + ] + if (current_cpu == "x86" || current_cpu == "x64") { sources += [ "zlib/crc_folding.c", diff --git a/tools/BUILD.gn b/tools/BUILD.gn index cd1e95f4..5e0228b9 100644 --- a/tools/BUILD.gn +++ b/tools/BUILD.gn @@ -22,80 +22,83 @@ source_set("tool_support") { public_configs = [ "..:crashpad_config" ] - deps = [ - "../third_party/mini_chromium:base", - ] + deps = [ "../third_party/mini_chromium:base" ] } -crashpad_executable("crashpad_database_util") { - sources = [ - "crashpad_database_util.cc", - ] +if (!crashpad_is_ios) { + crashpad_executable("crashpad_database_util") { + sources = [ "crashpad_database_util.cc" ] - deps = [ - ":tool_support", - "../build:default_exe_manifest_win", - "../client", - "../compat", - "../third_party/mini_chromium:base", - "../util", - ] -} - -crashpad_executable("crashpad_http_upload") { - sources = [ - "crashpad_http_upload.cc", - ] - - deps = [ - ":tool_support", - "../build:default_exe_manifest_win", - "../compat", - "../third_party/mini_chromium:base", - "../util", - ] -} - -crashpad_executable("generate_dump") { - sources = [ - "generate_dump.cc", - ] - - deps = [ - ":tool_support", - "../build:default_exe_manifest_win", - "../compat", - "../minidump", - "../snapshot", - "../third_party/mini_chromium:base", - "../util", - ] - - if (crashpad_is_mac) { - # This would be better as a config so that it could be shared with - # exception_port_tool, but configs can’t alter “inputs”. - # https://crbug.com/781858. - inputs = [ - "mac/sectaskaccess_info.plist", - ] - ldflags = [ - "-sectcreate", - "__TEXT", - "__info_plist", - rebase_path(inputs[0], root_build_dir), + deps = [ + ":tool_support", + "../build:default_exe_manifest_win", + "../client", + "../compat", + "../third_party/mini_chromium:base", + "../util", ] } - if (crashpad_is_win) { - cflags = [ "/wd4201" ] # nonstandard extension used : nameless struct/union + crashpad_executable("crashpad_http_upload") { + sources = [ "crashpad_http_upload.cc" ] + + deps = [ + ":tool_support", + "../build:default_exe_manifest_win", + "../compat", + "../third_party/mini_chromium:base", + "../util", + ] + } +} + +crashpad_executable("base94_encoder") { + sources = [ "base94_encoder.cc" ] + deps = [ + ":tool_support", + "../build:default_exe_manifest_win", + "../third_party/mini_chromium:base", + "../util", + ] +} + +if (!crashpad_is_fuchsia && !crashpad_is_ios) { + crashpad_executable("generate_dump") { + sources = [ "generate_dump.cc" ] + + deps = [ + ":tool_support", + "../build:default_exe_manifest_win", + "../compat", + "../minidump", + "../snapshot", + "../third_party/mini_chromium:base", + "../util", + ] + + if (crashpad_is_mac) { + # This would be better as a config so that it could be shared with + # exception_port_tool, but configs can’t alter “inputs”. + # https://crbug.com/781858. + inputs = [ "mac/sectaskaccess_info.plist" ] + ldflags = [ + "-sectcreate", + "__TEXT", + "__info_plist", + rebase_path(inputs[0], root_build_dir), + ] + } + + if (crashpad_is_win) { + cflags = + [ "/wd4201" ] # nonstandard extension used : nameless struct/union + } } } if (crashpad_is_mac || crashpad_is_fuchsia) { crashpad_executable("run_with_crashpad") { - sources = [ - "run_with_crashpad.cc", - ] + sources = [ "run_with_crashpad.cc" ] deps = [ ":tool_support", @@ -109,9 +112,7 @@ if (crashpad_is_mac || crashpad_is_fuchsia) { if (crashpad_is_mac) { crashpad_executable("catch_exception_tool") { - sources = [ - "mac/catch_exception_tool.cc", - ] + sources = [ "mac/catch_exception_tool.cc" ] deps = [ ":tool_support", @@ -122,15 +123,11 @@ if (crashpad_is_mac) { } crashpad_executable("exception_port_tool") { - sources = [ - "mac/exception_port_tool.cc", - ] + sources = [ "mac/exception_port_tool.cc" ] # This would be better as a config so that it could be shared with # generate_dump, but configs can’t alter “inputs”. https://crbug.com/781858. - inputs = [ - "mac/sectaskaccess_info.plist", - ] + inputs = [ "mac/sectaskaccess_info.plist" ] ldflags = [ "-sectcreate", "__TEXT", @@ -147,9 +144,7 @@ if (crashpad_is_mac) { } crashpad_executable("on_demand_service_tool") { - sources = [ - "mac/on_demand_service_tool.mm", - ] + sources = [ "mac/on_demand_service_tool.mm" ] libs = [ "CoreFoundation.framework", diff --git a/tools/base94_encoder.cc b/tools/base94_encoder.cc new file mode 100644 index 00000000..1b184e9f --- /dev/null +++ b/tools/base94_encoder.cc @@ -0,0 +1,128 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "base/files/file_path.h" +#include "build/build_config.h" +#include "tools/tool_support.h" +#include "util/stream/file_encoder.h" + +namespace crashpad { +namespace { + +void Usage(const base::FilePath& me) { + fprintf(stderr, +"Usage: %" PRFilePath " [options] \n" +"Encode/Decode the given file\n" +"\n" +" -e, --encode compress and encode the input file to a base94 encoded" + " file\n" +" -d, --decode decode and decompress a base94 encoded file\n" +" --help display this help and exit\n" +" --version output version information and exit\n", + me.value().c_str()); + ToolSupport::UsageTail(me); +} + +int Base94EncoderMain(int argc, char* argv[]) { + const base::FilePath argv0( + ToolSupport::CommandLineArgumentToFilePathStringType(argv[0])); + const base::FilePath me(argv0.BaseName()); + + enum OptionFlags { + // “Short” (single-character) options. + kOptionEncode = 'e', + kOptionDecode = 'd', + + // Standard options. + kOptionHelp = -2, + kOptionVersion = -3, + }; + + struct Options { + bool encoding; + base::FilePath input_file; + base::FilePath output_file; + } options = {}; + + static constexpr option long_options[] = { + {"encode", no_argument, nullptr, kOptionEncode}, + {"decode", no_argument, nullptr, kOptionDecode}, + {"help", no_argument, nullptr, kOptionHelp}, + {"version", no_argument, nullptr, kOptionVersion}, + {nullptr, 0, nullptr, 0}, + }; + + bool encoding_valid = false; + int opt; + while ((opt = getopt_long(argc, argv, "de", long_options, nullptr)) != -1) { + switch (opt) { + case kOptionEncode: + options.encoding = true; + encoding_valid = true; + break; + case kOptionDecode: + options.encoding = false; + encoding_valid = true; + break; + case kOptionHelp: + Usage(me); + return EXIT_SUCCESS; + case kOptionVersion: + ToolSupport::Version(me); + return EXIT_SUCCESS; + default: + ToolSupport::UsageHint(me, nullptr); + return EXIT_FAILURE; + } + } + + if (!encoding_valid) { + ToolSupport::UsageHint(me, "Either -e or -d required"); + return EXIT_FAILURE; + } + + argc -= optind; + argv += optind; + if (argc != 2) { + ToolSupport::UsageHint(me, "Both input-file and output-file required"); + return EXIT_FAILURE; + } + + options.input_file = base::FilePath( + ToolSupport::CommandLineArgumentToFilePathStringType(argv[0])); + options.output_file = base::FilePath( + ToolSupport::CommandLineArgumentToFilePathStringType(argv[1])); + + FileEncoder encoder(options.encoding ? crashpad::FileEncoder::Mode::kEncode + : crashpad::FileEncoder::Mode::kDecode, + options.input_file, + options.output_file); + return encoder.Process() ? EXIT_SUCCESS : EXIT_FAILURE; +} + +} // namespace +} // namespace crashpad + +#if defined(OS_POSIX) +int main(int argc, char* argv[]) { + return crashpad::Base94EncoderMain(argc, argv); +} +#elif defined(OS_WIN) +int wmain(int argc, wchar_t* argv[]) { + return crashpad::ToolSupport::Wmain(argc, argv, crashpad::Base94EncoderMain); +} +#endif // OS_POSIX diff --git a/tools/base94_encoder.md b/tools/base94_encoder.md new file mode 100644 index 00000000..292c7a63 --- /dev/null +++ b/tools/base94_encoder.md @@ -0,0 +1,100 @@ + + +# base94_encoder(1) + +## Name + +base94_encoder—Encode/Decode the given file + +## Synopsis + +**base94_encoder** [_OPTION…_] input-file output-file + +## Description + +Encodes a file for printing safely by compressing and base94 encoding it. + +The base94_encoder can decode the input file by base94 decoding and +uncompressing it. + +## Options + + * **-e**, **--encode** + + Compress and encode the input file to a base94 encoded file. + + * **-d**, **--decode** + + Decode and decompress a base94 encoded file. + + * **--help** + + Display help and exit. + + * **--version** + + Output version information and exit. + +## Examples + +Encode file a to b: + +``` +$ base94_encoder --encode a b +``` + +Decode file b to a + +``` +$ base94_encoder --decode b a +``` + +## Exit Status + + * **0** + + Success. + + * **1** + + Failure, with a message printed to the standard error stream. + + +## Resources + +Crashpad home page: https://crashpad.chromium.org/. + +Report bugs at https://crashpad.chromium.org/bug/new. + +## Copyright + +Copyright 2020 [The Crashpad +Authors](https://chromium.googlesource.com/crashpad/crashpad/+/master/AUTHORS). + +## License + +Licensed under the Apache License, Version 2.0 (the “License”); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/tools/crashpad_database_util.cc b/tools/crashpad_database_util.cc index b4c2a15b..a0036163 100644 --- a/tools/crashpad_database_util.cc +++ b/tools/crashpad_database_util.cc @@ -28,8 +28,8 @@ #include "base/files/file_path.h" #include "base/logging.h" -#include "base/macros.h" #include "base/numerics/safe_conversions.h" +#include "base/stl_util.h" #include "base/strings/utf_string_conversions.h" #include "build/build_config.h" #include "client/crash_report_database.h" @@ -109,14 +109,14 @@ bool StringToBool(const char* string, bool* boolean) { "set", }; - for (size_t index = 0; index < arraysize(kFalseWords); ++index) { + for (size_t index = 0; index < base::size(kFalseWords); ++index) { if (strcasecmp(string, kFalseWords[index]) == 0) { *boolean = false; return true; } } - for (size_t index = 0; index < arraysize(kTrueWords); ++index) { + for (size_t index = 0; index < base::size(kTrueWords); ++index) { if (strcasecmp(string, kTrueWords[index]) == 0) { *boolean = true; return true; @@ -159,7 +159,7 @@ bool StringToTime(const char* string, time_t* out_time, bool utc) { "%+", }; - for (size_t index = 0; index < arraysize(kFormats); ++index) { + for (size_t index = 0; index < base::size(kFormats); ++index) { tm time_tm; const char* strptime_result = strptime(string, kFormats[index], &time_tm); if (strptime_result == end) { @@ -214,7 +214,7 @@ std::string TimeToString(time_t out_time, bool utc) { char string[64]; CHECK_NE( - strftime(string, arraysize(string), "%Y-%m-%d %H:%M:%S %Z", &time_tm), + strftime(string, base::size(string), "%Y-%m-%d %H:%M:%S %Z", &time_tm), 0u); return std::string(string); @@ -561,7 +561,7 @@ int DatabaseUtilMain(int argc, char* argv[]) { } bool used_stdin = false; - for (const base::FilePath new_report_path : options.new_report_paths) { + for (const base::FilePath& new_report_path : options.new_report_paths) { std::unique_ptr file_reader; if (new_report_path.value() == FILE_PATH_LITERAL("-")) { diff --git a/tools/generate_dump.cc b/tools/generate_dump.cc index 2cdaed27..2113a1ab 100644 --- a/tools/generate_dump.cc +++ b/tools/generate_dump.cc @@ -26,11 +26,13 @@ #include "minidump/minidump_file_writer.h" #include "tools/tool_support.h" #include "util/file/file_writer.h" -#include "util/posix/drop_privileges.h" +#include "util/process/process_id.h" #include "util/stdlib/string_number_conversion.h" #if defined(OS_POSIX) #include + +#include "util/posix/drop_privileges.h" #endif #if defined(OS_MACOSX) @@ -45,11 +47,6 @@ #include "snapshot/win/process_snapshot_win.h" #include "util/win/scoped_process_suspend.h" #include "util/win/xp_compat.h" -#elif defined(OS_FUCHSIA) -#include "base/fuchsia/scoped_zx_handle.h" -#include "snapshot/fuchsia/process_snapshot_fuchsia.h" -#include "util/fuchsia/koid_utilities.h" -#include "util/fuchsia/scoped_task_suspend.h" #elif defined(OS_LINUX) || defined(OS_ANDROID) #include "snapshot/linux/process_snapshot_linux.h" #endif // OS_MACOSX @@ -90,7 +87,7 @@ int GenerateDumpMain(int argc, char* argv[]) { struct { std::string dump_path; - pid_t pid; + ProcessID pid; bool suspend; } options = {}; options.suspend = true; @@ -164,16 +161,11 @@ 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()) { - options.dump_path = base::StringPrintf("minidump.%d", options.pid); + options.dump_path = base::StringPrintf("minidump.%" PRI_PROCESS_ID, + options.pid); } { @@ -187,11 +179,6 @@ 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) @@ -209,11 +196,6 @@ 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; diff --git a/tools/mac/catch_exception_tool.cc b/tools/mac/catch_exception_tool.cc index d4dc3c12..4f40372c 100644 --- a/tools/mac/catch_exception_tool.cc +++ b/tools/mac/catch_exception_tool.cc @@ -28,6 +28,7 @@ #include "base/logging.h" #include "base/mac/mach_logging.h" #include "tools/tool_support.h" +#include "util/mach/bootstrap.h" #include "util/mach/exc_server_variants.h" #include "util/mach/exception_behaviors.h" #include "util/mach/exception_types.h" diff --git a/tools/mac/exception_port_tool.cc b/tools/mac/exception_port_tool.cc index a047e964..b9d3fdd6 100644 --- a/tools/mac/exception_port_tool.cc +++ b/tools/mac/exception_port_tool.cc @@ -30,6 +30,7 @@ #include "base/macros.h" #include "base/strings/stringprintf.h" #include "tools/tool_support.h" +#include "util/mach/bootstrap.h" #include "util/mach/exception_ports.h" #include "util/mach/mach_extensions.h" #include "util/mach/symbolic_constants_mach.h" diff --git a/util/BUILD.gn b/util/BUILD.gn index 03220c9e..d32863ad 100644 --- a/util/BUILD.gn +++ b/util/BUILD.gn @@ -13,13 +13,17 @@ # limitations under the License. import("../build/crashpad_buildconfig.gni") +import("net/tls.gni") -declare_args() { - use_boringssl_for_http_transport_socket = crashpad_is_fuchsia +if (crashpad_is_in_chromium) { + import("//build/config/sanitizers/sanitizers.gni") + + # Prevent Chromium source assignment filters from being inherited. + set_sources_assignment_filter([]) } -if (crashpad_is_mac) { - if (crashpad_is_in_chromium) { +if (crashpad_is_mac || crashpad_is_ios) { + if (crashpad_is_in_chromium || crashpad_is_in_fuchsia) { import("//build/config/sysroot.gni") } else { import("//third_party/mini_chromium/mini_chromium/build/sysroot.gni") @@ -27,14 +31,26 @@ if (crashpad_is_mac) { action_foreach("mig") { script = "mach/mig.py" - - sources = [ - "$sysroot/usr/include/mach/exc.defs", - "$sysroot/usr/include/mach/mach_exc.defs", - "$sysroot/usr/include/mach/notify.defs", - "mach/child_port.defs", + inputs = [ + "mach/mig_fix.py", + "mach/mig_gen.py", ] + if (crashpad_is_mac) { + sources = [ + "$sysroot/usr/include/mach/exc.defs", + "$sysroot/usr/include/mach/mach_exc.defs", + "$sysroot/usr/include/mach/notify.defs", + "mach/child_port.defs", + ] + } else if (crashpad_is_ios) { + sources = [ + # The iOS SDK doesn’t have any .defs files. Get them directly from xnu. + "../third_party/xnu/osfmk/mach/exc.defs", + "../third_party/xnu/osfmk/mach/mach_exc.defs", + ] + } + outputs = [ "$target_gen_dir/mach/{{source_name_part}}User.c", "$target_gen_dir/mach/{{source_name_part}}Server.c", @@ -46,9 +62,20 @@ if (crashpad_is_mac) { args += rebase_path(outputs, root_build_dir) if (crashpad_is_in_chromium) { if (!use_system_xcode) { + import("//build/config/clang/clang.gni") + import("//build/config/mac/mac_sdk.gni") + clang_path = + rebase_path("$clang_base_path/bin/", root_build_dir) + "clang" + mig_path = "$mac_bin_path" + "mig" + migcom_path = "$mac_bin_path" + "../libexec/migcom" + args += [ - "--developer-dir", - hermetic_xcode_path, + "--clang-path", + clang_path, + "--mig-path", + mig_path, + "--migcom-path", + migcom_path, ] } } @@ -59,9 +86,49 @@ if (crashpad_is_mac) { ] } args += [ + "--include", + rebase_path("..", root_build_dir), "--include", rebase_path("../compat/mac", root_build_dir), ] + if (crashpad_is_ios) { + args += [ + "--include", + rebase_path("../compat/ios", root_build_dir), + ] + } + if (current_cpu == "x86") { + args += [ + "--arch", + "i386", + ] + } else if (current_cpu == "x64") { + args += [ + "--arch", + "x86_64", + ] + } else if (current_cpu == "arm") { + args += [ + "--arch", + "armv7", + ] + } else if (current_cpu == "arm64") { + args += [ + "--arch", + "arm64", + ] + } + } + + static_library("mig_output") { + deps = [ ":mig" ] + sources = get_target_outputs(":mig") + if (crashpad_is_in_chromium) { + # mig output contains unreachable code, which irks -Wunreachable-code. + configs -= [ "//build/config/compiler:chromium_code" ] + configs += [ "//build/config/compiler:no_chromium_code" ] + } + public_configs = [ "..:crashpad_config" ] } } @@ -79,13 +146,15 @@ static_library("util") { "file/file_writer.cc", "file/file_writer.h", "file/filesystem.h", + "file/output_stream_file_writer.cc", + "file/output_stream_file_writer.h", "file/scoped_remove_file.cc", "file/scoped_remove_file.h", "file/string_file.cc", "file/string_file.h", "misc/address_sanitizer.h", "misc/address_types.h", - "misc/arraysize_unsafe.h", + "misc/arraysize.h", "misc/as_underlying_type.h", "misc/capture_context.h", "misc/clock.h", @@ -97,6 +166,7 @@ static_library("util") { "misc/initialization_state_dcheck.h", "misc/lexing.cc", "misc/lexing.h", + "misc/memory_sanitizer.h", "misc/metrics.cc", "misc/metrics.h", "misc/paths.h", @@ -136,6 +206,12 @@ static_library("util") { "numeric/in_range_cast.h", "numeric/int128.h", "numeric/safe_assignment.h", + "process/process_id.h", + "process/process_memory.cc", + "process/process_memory.h", + "process/process_memory_native.h", + "process/process_memory_range.cc", + "process/process_memory_range.h", "stdlib/aligned_allocator.cc", "stdlib/aligned_allocator.h", "stdlib/map_insert.h", @@ -147,6 +223,17 @@ static_library("util") { "stdlib/strnlen.cc", "stdlib/strnlen.h", "stdlib/thread_safe_vector.h", + "stream/base94_output_stream.cc", + "stream/base94_output_stream.h", + "stream/file_encoder.cc", + "stream/file_encoder.h", + "stream/file_output_stream.cc", + "stream/file_output_stream.h", + "stream/log_output_stream.cc", + "stream/log_output_stream.h", + "stream/output_stream_interface.h", + "stream/zlib_output_stream.cc", + "stream/zlib_output_stream.h", "string/split_string.cc", "string/split_string.h", "synchronization/semaphore.h", @@ -159,6 +246,8 @@ static_library("util") { "thread/worker_thread.h", ] + defines = [ "ZLIB_CONST" ] + if (crashpad_is_posix || crashpad_is_fuchsia) { sources += [ "file/directory_reader_posix.cc", @@ -196,22 +285,10 @@ static_library("util") { } } - if (crashpad_is_mac) { + if (crashpad_is_mac || crashpad_is_ios) { sources += [ - "mac/checked_mach_address_range.h", - "mac/launchd.h", - "mac/launchd.mm", - "mac/mac_util.cc", - "mac/mac_util.h", - "mac/service_management.cc", - "mac/service_management.h", "mac/xattr.cc", "mac/xattr.h", - "mach/child_port_handshake.cc", - "mach/child_port_handshake.h", - "mach/child_port_server.cc", - "mach/child_port_server.h", - "mach/child_port_types.h", "mach/composite_mach_message_server.cc", "mach/composite_mach_message_server.h", "mach/exc_client_variants.cc", @@ -222,14 +299,37 @@ static_library("util") { "mach/exception_behaviors.h", "mach/exception_ports.cc", "mach/exception_ports.h", - "mach/exception_types.cc", - "mach/exception_types.h", "mach/mach_extensions.cc", "mach/mach_extensions.h", "mach/mach_message.cc", "mach/mach_message.h", "mach/mach_message_server.cc", "mach/mach_message_server.h", + "misc/capture_context_mac.S", + "misc/clock_mac.cc", + "misc/paths_mac.cc", + "synchronization/semaphore_mac.cc", + ] + } + + if (crashpad_is_mac) { + sources += [ + "mac/checked_mach_address_range.h", + "mac/launchd.h", + "mac/launchd.mm", + "mac/mac_util.cc", + "mac/mac_util.h", + "mac/service_management.cc", + "mac/service_management.h", + "mach/bootstrap.cc", + "mach/bootstrap.h", + "mach/child_port_handshake.cc", + "mach/child_port_handshake.h", + "mach/child_port_server.cc", + "mach/child_port_server.h", + "mach/child_port_types.h", + "mach/exception_types.cc", + "mach/exception_types.h", "mach/notify_server.cc", "mach/notify_server.h", "mach/scoped_task_suspend.cc", @@ -238,26 +338,30 @@ static_library("util") { "mach/symbolic_constants_mach.h", "mach/task_for_pid.cc", "mach/task_for_pid.h", - "mach/task_memory.cc", - "mach/task_memory.h", - "misc/capture_context_mac.S", - "misc/clock_mac.cc", - "misc/paths_mac.cc", "net/http_transport_mac.mm", "posix/process_info_mac.cc", - "synchronization/semaphore_mac.cc", + "process/process_memory_mac.cc", + "process/process_memory_mac.h", + ] + } + + if (crashpad_is_ios) { + sources += [ + "ios/exception_processor.h", + "ios/exception_processor.mm", + "ios/ios_system_data_collector.h", + "ios/ios_system_data_collector.mm", ] - sources += get_target_outputs(":mig") } deps = [] - if (crashpad_is_linux || crashpad_is_fuchsia) { + if (crashpad_is_linux || crashpad_is_fuchsia || crashpad_is_android) { sources += [ "net/http_transport_socket.cc" ] - if (use_boringssl_for_http_transport_socket) { - defines = [ "CRASHPAD_USE_BORINGSSL" ] + if (crashpad_use_boringssl_for_http_transport_socket) { + defines += [ "CRASHPAD_USE_BORINGSSL" ] - if (crashpad_is_in_fuchsia) { + if (crashpad_is_in_chromium || crashpad_is_in_fuchsia) { deps += [ "//third_party/boringssl" ] } else { libs = [ @@ -266,12 +370,16 @@ static_library("util") { ] } } - } else if (crashpad_is_android) { - sources += [ "net/http_transport_none.cc" ] + } + + if (crashpad_is_android) { + sources += [ + "linux/initial_signal_dispositions.cc", + "linux/initial_signal_dispositions.h", + ] } if (crashpad_is_linux || crashpad_is_android) { - set_sources_assignment_filter([]) sources += [ "linux/address_types.h", "linux/auxiliary_vector.cc", @@ -288,6 +396,8 @@ static_library("util") { "linux/memory_map.h", "linux/proc_stat_reader.cc", "linux/proc_stat_reader.h", + "linux/proc_task_reader.cc", + "linux/proc_task_reader.h", "linux/ptrace_broker.cc", "linux/ptrace_broker.h", "linux/ptrace_client.cc", @@ -295,30 +405,25 @@ static_library("util") { "linux/ptrace_connection.h", "linux/ptracer.cc", "linux/ptracer.h", + "linux/scoped_pr_set_dumpable.cc", + "linux/scoped_pr_set_dumpable.h", "linux/scoped_pr_set_ptracer.cc", "linux/scoped_pr_set_ptracer.h", "linux/scoped_ptrace_attach.cc", "linux/scoped_ptrace_attach.h", + "linux/socket.cc", + "linux/socket.h", "linux/thread_info.cc", "linux/thread_info.h", "linux/traits.h", "misc/capture_context_linux.S", "misc/paths_linux.cc", + "misc/time_linux.cc", "posix/process_info_linux.cc", "process/process_memory_linux.cc", "process/process_memory_linux.h", - ] - } - - if (crashpad_is_linux || crashpad_is_android || crashpad_is_fuchsia) { - sources += [ - "process/process_memory.cc", - "process/process_memory.h", - "process/process_memory_native.h", - - # TODO: Port to all platforms. - "process/process_memory_range.cc", - "process/process_memory_range.h", + "process/process_memory_sanitized.cc", + "process/process_memory_sanitized.h", ] } @@ -331,12 +436,15 @@ static_library("util") { "misc/paths_win.cc", "misc/time_win.cc", "net/http_transport_win.cc", + "process/process_memory_win.cc", + "process/process_memory_win.h", "synchronization/semaphore_win.cc", "thread/thread_win.cc", "win/address_types.h", "win/checked_win_address_range.h", "win/command_line.cc", "win/command_line.h", + "win/context_wrappers.h", "win/critical_section_with_debug_info.cc", "win/critical_section_with_debug_info.h", "win/exception_handler_server.cc", @@ -349,6 +457,8 @@ static_library("util") { "win/handle.h", "win/initial_client_data.cc", "win/initial_client_data.h", + "win/loader_lock.cc", + "win/loader_lock.h", "win/module_version.cc", "win/module_version.h", "win/nt_internals.cc", @@ -386,10 +496,24 @@ static_library("util") { # TODO(thakis): Use the .asm file in cross builds somehow, # https://crbug.com/762167. if (host_os == "win") { - sources += [ - "misc/capture_context_win.asm", - "win/safe_terminate_process.asm", - ] + if (current_cpu != "arm64") { + sources += [ + "misc/capture_context_win.asm", + "win/safe_terminate_process.asm", + ] + } else { + # Most Crashpad builds use Microsoft's armasm64.exe macro assembler for + # .asm source files. When building in Chromium, clang-cl is used as the + # assembler instead. Since the two assemblers recognize different + # assembly dialects, the same .asm file can't be used for each. As a + # workaround, use a prebuilt .obj file when the Microsoft-dialect + # assembler isn't available. + if (crashpad_is_in_chromium) { + sources += [ "misc/capture_context_win_arm64.obj" ] + } else { + sources += [ "misc/capture_context_win_arm64.asm" ] + } + } } else { sources += [ "misc/capture_context_broken.cc", @@ -404,7 +528,6 @@ static_library("util") { "fuchsia/koid_utilities.h", "fuchsia/scoped_task_suspend.cc", "fuchsia/scoped_task_suspend.h", - "fuchsia/system_exception_port_key.h", "misc/capture_context_fuchsia.S", "misc/paths_fuchsia.cc", "process/process_memory_fuchsia.cc", @@ -415,17 +538,24 @@ static_library("util") { public_configs = [ "..:crashpad_config" ] # Include generated files starting with "util". - include_dirs = [ "$root_gen_dir/third_party/crashpad/crashpad" ] + if (crashpad_is_in_fuchsia) { + include_dirs = [ "$root_gen_dir/third_party/crashpad" ] + } else { + include_dirs = [ "$root_gen_dir/third_party/crashpad/crashpad" ] + } public_deps = [ "../compat", - ] - - deps += [ - "../third_party/mini_chromium:base", "../third_party/zlib", ] + deps += [ "../third_party/mini_chromium:base" ] + + if (crashpad_is_mac || crashpad_is_ios) { + include_dirs += [ "$root_build_dir/gen" ] + deps += [ ":mig_output" ] + } + if (crashpad_is_mac) { libs = [ "bsm", @@ -433,8 +563,7 @@ static_library("util") { "Foundation.framework", "IOKit.framework", ] - deps += [ ":mig" ] - include_dirs += [ "$root_build_dir/gen" ] + deps += [ "../third_party/apple_cf:apple_cf" ] } if (crashpad_is_win) { @@ -454,25 +583,22 @@ static_library("util") { asmflags = [ "/safeseh" ] } } -} -if (use_boringssl_for_http_transport_socket) { - action("generate_test_server_key") { - script = "net/generate_test_server_key.py" - outputs = [ - "$root_out_dir/crashpad_util_test_cert.pem", - "$root_out_dir/crashpad_util_test_key.pem", - ] - data = outputs + if (crashpad_is_fuchsia) { + public_deps += [ "../third_party/fuchsia" ] } + + if (crashpad_is_android || crashpad_is_linux) { + deps += [ "../third_party/lss" ] + } + + configs += [ "..:disable_ubsan" ] } -if (!crashpad_is_android) { +if (!crashpad_is_android && !crashpad_is_ios) { crashpad_executable("http_transport_test_server") { testonly = true - sources = [ - "net/http_transport_test_server.cc", - ] + sources = [ "net/http_transport_test_server.cc" ] deps = [ ":util", @@ -490,13 +616,10 @@ if (!crashpad_is_android) { libs = [ "ws2_32.lib" ] } - if (use_boringssl_for_http_transport_socket) { - data_deps = [ - ":generate_test_server_key", - ] + if (crashpad_use_boringssl_for_http_transport_socket) { defines = [ "CRASHPAD_USE_BORINGSSL" ] - if (crashpad_is_in_fuchsia) { + if (crashpad_is_in_chromium || crashpad_is_in_fuchsia) { deps += [ "//third_party/boringssl" ] } else { libs = [ @@ -518,7 +641,7 @@ source_set("util_test") { "file/file_reader_test.cc", "file/filesystem_test.cc", "file/string_file_test.cc", - "misc/arraysize_unsafe_test.cc", + "misc/arraysize_test.cc", "misc/capture_context_test.cc", "misc/capture_context_test_util.h", "misc/clock_test.cc", @@ -542,12 +665,20 @@ source_set("util_test") { "numeric/checked_range_test.cc", "numeric/in_range_cast_test.cc", "numeric/int128_test.cc", + "process/process_memory_range_test.cc", + "process/process_memory_test.cc", "stdlib/aligned_allocator_test.cc", "stdlib/map_insert_test.cc", "stdlib/string_number_conversion_test.cc", "stdlib/strlcpy_test.cc", "stdlib/strnlen_test.cc", "stdlib/thread_safe_vector_test.cc", + "stream/base94_output_stream_test.cc", + "stream/file_encoder_test.cc", + "stream/log_output_stream_test.cc", + "stream/test_output_stream.cc", + "stream/test_output_stream.h", + "stream/zlib_output_stream_test.cc", "string/split_string_test.cc", "synchronization/semaphore_test.cc", "thread/thread_log_messages_test.cc", @@ -555,13 +686,13 @@ source_set("util_test") { "thread/worker_thread_test.cc", ] - if (!crashpad_is_android) { + if (!crashpad_is_android && !crashpad_is_ios) { # Android requires an HTTPTransport implementation. sources += [ "net/http_transport_test.cc" ] } if (crashpad_is_posix || crashpad_is_fuchsia) { - if (!crashpad_is_fuchsia) { + if (!crashpad_is_fuchsia && !crashpad_is_ios) { sources += [ "posix/process_info_test.cc", "posix/signals_test.cc", @@ -571,41 +702,59 @@ source_set("util_test") { sources += [ "posix/scoped_mmap_test.cc" ] } + if (crashpad_is_mac || crashpad_is_ios) { + sources += [ + "mac/xattr_test.cc", + "mach/composite_mach_message_server_test.cc", + "mach/exc_server_variants_test.cc", + "mach/exception_behaviors_test.cc", + "mach/mach_extensions_test.cc", + "mach/mach_message_test.cc", + ] + } + if (crashpad_is_mac) { sources += [ "mac/launchd_test.mm", "mac/mac_util_test.mm", "mac/service_management_test.mm", - "mac/xattr_test.cc", + "mach/bootstrap_test.cc", "mach/child_port_handshake_test.cc", "mach/child_port_server_test.cc", - "mach/composite_mach_message_server_test.cc", "mach/exc_client_variants_test.cc", - "mach/exc_server_variants_test.cc", - "mach/exception_behaviors_test.cc", "mach/exception_ports_test.cc", "mach/exception_types_test.cc", - "mach/mach_extensions_test.cc", "mach/mach_message_server_test.cc", - "mach/mach_message_test.cc", "mach/notify_server_test.cc", "mach/scoped_task_suspend_test.cc", "mach/symbolic_constants_mach_test.cc", - "mach/task_memory_test.cc", "misc/capture_context_test_util_mac.cc", + "process/process_memory_mac_test.cc", + ] + } + + if (crashpad_is_ios) { + sources += [ "ios/exception_processor_test.mm" ] + + sources -= [ + "misc/capture_context_test.cc", + "process/process_memory_range_test.cc", + "process/process_memory_test.cc", ] } if (crashpad_is_linux || crashpad_is_android) { - set_sources_assignment_filter([]) sources += [ "linux/auxiliary_vector_test.cc", "linux/memory_map_test.cc", "linux/proc_stat_reader_test.cc", + "linux/proc_task_reader_test.cc", "linux/ptrace_broker_test.cc", "linux/ptracer_test.cc", "linux/scoped_ptrace_attach_test.cc", + "linux/socket_test.cc", "misc/capture_context_test_util_linux.cc", + "process/process_memory_sanitized_test.cc", ] } @@ -613,14 +762,6 @@ source_set("util_test") { sources += [ "misc/capture_context_test_util_fuchsia.cc" ] } - if (crashpad_is_linux || crashpad_is_android || crashpad_is_fuchsia) { - sources += [ - # TODO: Port to all platforms. - "process/process_memory_range_test.cc", - "process/process_memory_test.cc", - ] - } - if (crashpad_is_win) { sources += [ "misc/capture_context_test_util_win.cc", @@ -630,16 +771,25 @@ source_set("util_test") { "win/get_function_test.cc", "win/handle_test.cc", "win/initial_client_data_test.cc", + "win/loader_lock_test.cc", "win/process_info_test.cc", "win/registration_protocol_win_test.cc", "win/safe_terminate_process_test.cc", "win/scoped_process_suspend_test.cc", "win/session_end_watcher_test.cc", ] + + if (crashpad_is_in_chromium && is_asan && is_component_build) { + # TODO(crbug.com/856174): Re-enable these once Windows ASan is fixed. + sources -= [ "stdlib/string_number_conversion_test.cc" ] + } } data = [ - "net/testdata/", + "net/testdata/ascii_http_body.txt", + "net/testdata/binary_http_body.dat", + "net/testdata/crashpad_util_test_cert.pem", + "net/testdata/crashpad_util_test_key.pem", ] deps = [ @@ -653,12 +803,10 @@ source_set("util_test") { "../third_party/zlib", ] - if (!crashpad_is_android) { - data_deps = [ - ":http_transport_test_server", - ] + if (!crashpad_is_android && !crashpad_is_ios) { + data_deps = [ ":http_transport_test_server" ] - if (use_boringssl_for_http_transport_socket) { + if (crashpad_use_boringssl_for_http_transport_socket) { defines = [ "CRASHPAD_USE_BORINGSSL" ] } } @@ -667,30 +815,55 @@ source_set("util_test") { libs = [ "Foundation.framework" ] } + if (crashpad_is_ios) { + deps += [ ":util_test_bundle_data" ] + } + + if (crashpad_is_android || crashpad_is_linux) { + deps += [ "../third_party/lss" ] + } + if (crashpad_is_win) { libs = [ "rpcrt4.lib", "dbghelp.lib", ] data_deps += [ + ":crashpad_util_test_loader_lock_test", ":crashpad_util_test_process_info_test_child", ":crashpad_util_test_safe_terminate_process_test_child", ] } } +if (crashpad_is_ios) { + bundle_data("util_test_bundle_data") { + testonly = true + + sources = [ + "net/testdata/ascii_http_body.txt", + "net/testdata/binary_http_body.dat", + ] + + outputs = [ "{{bundle_resources_dir}}/crashpad_test_data/" + + "{{source_root_relative_dir}}/{{source_file_part}}" ] + } +} + if (crashpad_is_win) { crashpad_executable("crashpad_util_test_process_info_test_child") { testonly = true - sources = [ - "win/process_info_test_child.cc", - ] + sources = [ "win/process_info_test_child.cc" ] } crashpad_executable("crashpad_util_test_safe_terminate_process_test_child") { testonly = true - sources = [ - "win/safe_terminate_process_test_child.cc", - ] + sources = [ "win/safe_terminate_process_test_child.cc" ] + } + + crashpad_loadable_module("crashpad_util_test_loader_lock_test") { + testonly = true + sources = [ "win/loader_lock_test_dll.cc" ] + deps = [ ":util" ] } } diff --git a/util/file/delimited_file_reader.cc b/util/file/delimited_file_reader.cc index a55c2a7c..2275adef 100644 --- a/util/file/delimited_file_reader.cc +++ b/util/file/delimited_file_reader.cc @@ -21,6 +21,7 @@ #include "base/logging.h" #include "base/numerics/safe_conversions.h" +#include "base/stl_util.h" namespace crashpad { @@ -75,7 +76,7 @@ DelimitedFileReader::Result DelimitedFileReader::GetDelim(char delimiter, return Result::kEndOfFile; } - DCHECK_LE(static_cast(read_result), arraysize(buf_)); + DCHECK_LE(static_cast(read_result), base::size(buf_)); DCHECK( base::IsValueInRangeForNumericType(read_result)); buf_len_ = static_cast(read_result); diff --git a/util/file/delimited_file_reader_test.cc b/util/file/delimited_file_reader_test.cc index 79e331f7..a2fd7d5c 100644 --- a/util/file/delimited_file_reader_test.cc +++ b/util/file/delimited_file_reader_test.cc @@ -17,6 +17,7 @@ #include #include "base/format_macros.h" +#include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "gtest/gtest.h" #include "util/file/string_file.h" @@ -259,7 +260,7 @@ TEST(DelimitedFileReader, ReallyLongMultiLineFile) { TEST(DelimitedFileReader, EmbeddedNUL) { static constexpr char kString[] = "embedded\0NUL\n"; StringFile string_file; - string_file.SetString(std::string(kString, arraysize(kString) - 1)); + string_file.SetString(std::string(kString, base::size(kString) - 1)); DelimitedFileReader delimited_file_reader(&string_file); std::string line; @@ -277,7 +278,7 @@ TEST(DelimitedFileReader, EmbeddedNUL) { TEST(DelimitedFileReader, NULDelimiter) { static constexpr char kString[] = "aa\0b\0ccc\0"; StringFile string_file; - string_file.SetString(std::string(kString, arraysize(kString) - 1)); + string_file.SetString(std::string(kString, base::size(kString) - 1)); DelimitedFileReader delimited_file_reader(&string_file); std::string field; @@ -301,7 +302,7 @@ TEST(DelimitedFileReader, NULDelimiter) { TEST(DelimitedFileReader, EdgeCases) { static constexpr size_t kSizes[] = {4094, 4095, 4096, 4097, 8190, 8191, 8192, 8193}; - for (size_t index = 0; index < arraysize(kSizes); ++index) { + for (size_t index = 0; index < base::size(kSizes); ++index) { size_t size = kSizes[index]; SCOPED_TRACE( base::StringPrintf("index %" PRIuS ", size %" PRIuS, index, size)); diff --git a/util/file/directory_reader_posix.cc b/util/file/directory_reader_posix.cc index b33e19bb..475b0b18 100644 --- a/util/file/directory_reader_posix.cc +++ b/util/file/directory_reader_posix.cc @@ -39,7 +39,7 @@ DirectoryReader::~DirectoryReader() {} bool DirectoryReader::Open(const base::FilePath& path) { dir_.reset(HANDLE_EINTR_IF_EQ(opendir(path.value().c_str()), nullptr)); if (!dir_.is_valid()) { - PLOG(ERROR) << "opendir"; + PLOG(ERROR) << "opendir " << path.value(); return false; } return true; @@ -52,7 +52,7 @@ DirectoryReader::Result DirectoryReader::NextFile(base::FilePath* filename) { dirent* entry = HANDLE_EINTR_IF_EQ(readdir(dir_.get()), nullptr); if (!entry) { if (errno) { - PLOG(ERROR) << "readdir"; + PLOG(ERROR) << "readdir " << filename->value(); return Result::kError; } else { return Result::kNoMoreFiles; diff --git a/util/file/directory_reader_test.cc b/util/file/directory_reader_test.cc index f03669e2..812deaf1 100644 --- a/util/file/directory_reader_test.cc +++ b/util/file/directory_reader_test.cc @@ -22,7 +22,6 @@ #include "base/strings/utf_string_conversions.h" #include "gtest/gtest.h" #include "test/filesystem.h" -#include "test/gtest_disabled.h" #include "test/scoped_temp_dir.h" #include "util/file/file_io.h" #include "util/file/filesystem.h" @@ -48,7 +47,7 @@ TEST(DirectoryReader, BadPaths) { TEST(DirectoryReader, BadPaths_SymbolicLinks) { if (!CanCreateSymbolicLinks()) { - DISABLED_TEST(); + GTEST_SKIP(); } ScopedTempDir temp_dir; @@ -144,7 +143,7 @@ TEST(DirectoryReader, FilesAndDirectories) { TEST(DirectoryReader, FilesAndDirectories_SymbolicLinks) { if (!CanCreateSymbolicLinks()) { - DISABLED_TEST(); + GTEST_SKIP(); } TestFilesAndDirectories(true); diff --git a/util/file/file_io.h b/util/file/file_io.h index 797db682..6fa0f96d 100644 --- a/util/file/file_io.h +++ b/util/file/file_io.h @@ -398,6 +398,30 @@ FileHandle LoggingOpenFileForWrite(const base::FilePath& path, FileWriteMode mode, FilePermissions permissions); +#if defined(OS_LINUX) +//! \brief Opens an in-memory file for input and output. +//! +//! This function first attempts to open the file with `memfd_create()`. If +//! `memfd_create()` isn't supported by the kernel, this function next attempts +//! to open a file using `O_TMPFILE`. If `O_TMPFILE` isn't supported, this +//! function finally falls back to creating a file with a randomized name in +//! `/tmp` and immediately `unlink()`ing it. +//! +//! Unlike other file open operations, this function doesn't set `O_CLOEXEC`. +//! +//! \param name A name associated with the file. This name does not indicate any +//! exact path and may not be used at all, depending on the strategy used to +//! create the file. The name should not contain any '/' characters. +//! \return The newly opened FileHandle, or an invalid FileHandle on failure, +//! with a message logged. +//! +//! \sa ScopedFileHandle +//! \sa LoggingOpenFileForRead +//! \sa LoggingOpenFileForWrite +//! \sa LoggingOpenFileForReadAndWrite +FileHandle LoggingOpenMemoryFileForReadAndWrite(const base::FilePath& name); +#endif // OS_LINUX + //! \brief Wraps OpenFileForReadAndWrite(), logging an error if the operation //! fails. //! diff --git a/util/file/file_io_posix.cc b/util/file/file_io_posix.cc index f3116169..91b252a0 100644 --- a/util/file/file_io_posix.cc +++ b/util/file/file_io_posix.cc @@ -14,8 +14,10 @@ #include "util/file/file_io.h" +#include #include #include +#include #include #include @@ -25,7 +27,9 @@ #include "base/files/file_path.h" #include "base/logging.h" #include "base/posix/eintr_wrapper.h" +#include "base/strings/stringprintf.h" #include "build/build_config.h" +#include "util/misc/random_string.h" namespace crashpad { @@ -97,7 +101,6 @@ FileHandle OpenFileForOutput(int rdwr_or_wronly, flags, permissions == FilePermissions::kWorldReadable ? 0644 : 0600)); } - } // namespace namespace internal { @@ -149,6 +152,53 @@ FileHandle LoggingOpenFileForWrite(const base::FilePath& path, return fd; } +#if defined(OS_LINUX) +FileHandle LoggingOpenMemoryFileForReadAndWrite(const base::FilePath& name) { + DCHECK(name.value().find('/') == std::string::npos); + + int result = HANDLE_EINTR(memfd_create(name.value().c_str(), 0)); + if (result >= 0 || errno != ENOSYS) { + PLOG_IF(ERROR, result < 0) << "memfd_create"; + return result; + } + + const char* tmp = getenv("TMPDIR"); + tmp = tmp ? tmp : "/tmp"; + + result = HANDLE_EINTR(open(tmp, O_RDWR | O_EXCL | O_TMPFILE, 0600)); + if (result >= 0 || + // These are the expected possible error codes indicating that O_TMPFILE + // doesn't have kernel or filesystem support. O_TMPFILE was added in Linux + // 3.11. Experimentation confirms that at least Linux 2.6.29 and Linux + // 3.10 set errno to EISDIR. EOPNOTSUPP is returned when the filesystem + // doesn't support O_TMPFILE. The man pages also mention ENOENT as an + // error code to check, but the language implies it would only occur when + // |tmp| is also an invalid directory. EINVAL is mentioned as a possible + // error code for any invalid values in flags, but O_TMPFILE isn't + // mentioned explicitly in this context and hasn't been observed in + // practice. + (errno != EISDIR && errno != EOPNOTSUPP)) { + PLOG_IF(ERROR, result < 0) << "open"; + return result; + } + + std::string path = base::StringPrintf("%s/%s.%d.%s", + tmp, + name.value().c_str(), + getpid(), + RandomString().c_str()); + result = HANDLE_EINTR(open(path.c_str(), O_RDWR | O_CREAT | O_EXCL, 0600)); + if (result < 0) { + PLOG(ERROR) << "open"; + return result; + } + if (unlink(path.c_str()) != 0) { + PLOG(WARNING) << "unlink"; + } + return result; +} +#endif + FileHandle LoggingOpenFileForReadAndWrite(const base::FilePath& path, FileWriteMode mode, FilePermissions permissions) { diff --git a/util/file/file_io_test.cc b/util/file/file_io_test.cc index 4446c320..0efded8a 100644 --- a/util/file/file_io_test.cc +++ b/util/file/file_io_test.cc @@ -22,6 +22,7 @@ #include "base/atomicops.h" #include "base/files/file_path.h" #include "base/macros.h" +#include "base/stl_util.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include "test/errors.h" @@ -472,6 +473,23 @@ TEST(FileIO, LoggingOpenFileForReadAndWrite) { TestOpenFileForWrite(LoggingOpenFileForReadAndWrite); } +#if defined(OS_LINUX) +TEST(FileIO, LoggingOpenMemoryFileForReadAndWrite) { + ScopedFileHandle handle( + LoggingOpenMemoryFileForReadAndWrite(base::FilePath("memfile"))); + ASSERT_TRUE(handle.is_valid()); + + static constexpr char kTestData[] = "somedata"; + ASSERT_TRUE(LoggingWriteFile(handle.get(), kTestData, sizeof(kTestData))); + + ASSERT_EQ(LoggingSeekFile(handle.get(), 0, SEEK_SET), 0); + + char buffer[sizeof(kTestData)]; + ASSERT_TRUE(LoggingReadFileExactly(handle.get(), buffer, sizeof(buffer))); + EXPECT_EQ(memcmp(buffer, kTestData, sizeof(buffer)), 0); +} +#endif // OS_LINUX + enum class ReadOrWrite : bool { kRead, kWrite, @@ -612,7 +630,7 @@ void LockingTest(FileLocking main_lock, FileLocking other_locks) { LockingTestThread threads[20]; int expected_iterations = 0; - for (size_t index = 0; index < arraysize(threads); ++index) { + for (size_t index = 0; index < base::size(threads); ++index) { int iterations_for_this_thread = static_cast(index * 10); threads[index].Init( (other_locks == FileLocking::kShared) diff --git a/util/file/file_io_win.cc b/util/file/file_io_win.cc index 7428034a..111a8da2 100644 --- a/util/file/file_io_win.cc +++ b/util/file/file_io_win.cc @@ -28,7 +28,7 @@ bool IsSocketHandle(HANDLE file) { // FILE_TYPE_PIPE means that it's a socket, a named pipe, or an anonymous // pipe. If we are unable to retrieve the pipe information, we know it's a // socket. - return !GetNamedPipeInfo(file, NULL, NULL, NULL, NULL); + return !GetNamedPipeInfo(file, nullptr, nullptr, nullptr, nullptr); } return false; } diff --git a/util/file/file_writer.cc b/util/file/file_writer.cc index 13ac6cf5..6dff975a 100644 --- a/util/file/file_writer.cc +++ b/util/file/file_writer.cc @@ -171,6 +171,23 @@ bool FileWriter::Open(const base::FilePath& path, return true; } +#if defined(OS_LINUX) +bool FileWriter::OpenMemfd(const base::FilePath& path) { + CHECK(!file_.is_valid()); + file_.reset(LoggingOpenMemoryFileForReadAndWrite(path)); + if (!file_.is_valid()) { + return false; + } + + weak_file_handle_file_writer_.set_file_handle(file_.get()); + return true; +} + +int FileWriter::fd() { + return file_.get(); +} +#endif + void FileWriter::Close() { CHECK(file_.is_valid()); diff --git a/util/file/file_writer.h b/util/file/file_writer.h index ed261ec5..4b99b375 100644 --- a/util/file/file_writer.h +++ b/util/file/file_writer.h @@ -131,6 +131,22 @@ class FileWriter : public FileWriterInterface { FileWriteMode write_mode, FilePermissions permissions); +#if defined(OS_LINUX) + //! \brief Wraps LoggingOpenMemoryFileForWrite(). + //! + //! \return `true` if the operation succeeded, `false` if it failed, with an + //! error message logged. + //! + //! \note After a successful call, this method or Open() cannot be called + // again until after Close(). + bool OpenMemfd(const base::FilePath& path); + + //! \brief Returns the underlying file descriptor. + //! + //! \note This is used when this writes to a Memfd. + int fd(); +#endif + //! \brief Wraps CheckedCloseHandle(). //! //! \note It is only valid to call this method on an object that has had a diff --git a/util/file/filesystem.h b/util/file/filesystem.h index 27a4d1de..e12f1271 100644 --- a/util/file/filesystem.h +++ b/util/file/filesystem.h @@ -73,7 +73,8 @@ bool MoveFileOrDirectory(const base::FilePath& source, //! failure. //! //! On POSIX, this function returns `true` if \a path refers to a file that is -//! not a symbolic link, directory, or other kind of special file. +//! not a symbolic link, directory, or other kind of special file. If this +//! function fails because \a path does not exist, then no message is logged. //! //! On Windows, this function returns `true` if \a path refers to a file that //! is not a symbolic link or directory. @@ -85,6 +86,9 @@ bool IsRegularFile(const base::FilePath& path); //! \brief Determines if a path refers to a directory, logging a message on //! failure. //! +//! On POSIX, if this function fails because \a path does not exist, then no +//! message is logged. +//! //! \param[in] path The path to check. //! \param[in] allow_symlinks Whether to allow the final component in the path //! to be a symbolic link to a directory. diff --git a/util/file/filesystem_posix.cc b/util/file/filesystem_posix.cc index c2234dae..bb51a9c1 100644 --- a/util/file/filesystem_posix.cc +++ b/util/file/filesystem_posix.cc @@ -65,14 +65,6 @@ 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(); @@ -84,7 +76,7 @@ bool MoveFileOrDirectory(const base::FilePath& source, bool IsRegularFile(const base::FilePath& path) { struct stat st; if (lstat(path.value().c_str(), &st) != 0) { - PLOG(ERROR) << "stat " << path.value(); + PLOG_IF(ERROR, errno != ENOENT) << "stat " << path.value(); return false; } return S_ISREG(st.st_mode); @@ -94,11 +86,11 @@ bool IsDirectory(const base::FilePath& path, bool allow_symlinks) { struct stat st; if (allow_symlinks) { if (stat(path.value().c_str(), &st) != 0) { - PLOG(ERROR) << "stat " << path.value(); + PLOG_IF(ERROR, errno != ENOENT) << "stat " << path.value(); return false; } } else if (lstat(path.value().c_str(), &st) != 0) { - PLOG(ERROR) << "lstat " << path.value(); + PLOG_IF(ERROR, errno != ENOENT) << "lstat " << path.value(); return false; } return S_ISDIR(st.st_mode); diff --git a/util/file/filesystem_test.cc b/util/file/filesystem_test.cc index 3430c3f9..37a9290b 100644 --- a/util/file/filesystem_test.cc +++ b/util/file/filesystem_test.cc @@ -21,7 +21,6 @@ #include "gtest/gtest.h" #include "test/errors.h" #include "test/filesystem.h" -#include "test/gtest_disabled.h" #include "test/scoped_temp_dir.h" #include "util/misc/time.h" @@ -93,7 +92,7 @@ TEST(Filesystem, FileModificationTime) { TEST(Filesystem, FileModificationTime_SymbolicLinks) { if (!CanCreateSymbolicLinks()) { - DISABLED_TEST(); + GTEST_SKIP(); } ScopedTempDir temp_dir; @@ -224,7 +223,7 @@ TEST(Filesystem, MoveFileOrDirectory) { TEST(Filesystem, MoveFileOrDirectory_SymbolicLinks) { if (!CanCreateSymbolicLinks()) { - DISABLED_TEST(); + GTEST_SKIP(); } ScopedTempDir temp_dir; @@ -302,7 +301,7 @@ TEST(Filesystem, IsRegularFile) { TEST(Filesystem, IsRegularFile_SymbolicLinks) { if (!CanCreateSymbolicLinks()) { - DISABLED_TEST(); + GTEST_SKIP(); } ScopedTempDir temp_dir; @@ -344,7 +343,7 @@ TEST(Filesystem, IsDirectory) { TEST(Filesystem, IsDirectory_SymbolicLinks) { if (!CanCreateSymbolicLinks()) { - DISABLED_TEST(); + GTEST_SKIP(); } ScopedTempDir temp_dir; @@ -393,7 +392,7 @@ TEST(Filesystem, RemoveFile) { TEST(Filesystem, RemoveFile_SymbolicLinks) { if (!CanCreateSymbolicLinks()) { - DISABLED_TEST(); + GTEST_SKIP(); } ScopedTempDir temp_dir; @@ -450,7 +449,7 @@ TEST(Filesystem, RemoveDirectory) { TEST(Filesystem, RemoveDirectory_SymbolicLinks) { if (!CanCreateSymbolicLinks()) { - DISABLED_TEST(); + GTEST_SKIP(); } ScopedTempDir temp_dir; diff --git a/util/file/output_stream_file_writer.cc b/util/file/output_stream_file_writer.cc new file mode 100644 index 00000000..1172019e --- /dev/null +++ b/util/file/output_stream_file_writer.cc @@ -0,0 +1,68 @@ +// Copyright 2020 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/file/output_stream_file_writer.h" + +#include "base/logging.h" +#include "util/stream/output_stream_interface.h" + +namespace crashpad { + +OutputStreamFileWriter::OutputStreamFileWriter( + std::unique_ptr output_stream) + : output_stream_(std::move(output_stream)), + flush_needed_(false), + flushed_(false) {} + +OutputStreamFileWriter::~OutputStreamFileWriter() { + DCHECK(!flush_needed_); +} + +bool OutputStreamFileWriter::Write(const void* data, size_t size) { + DCHECK(!flushed_); + flush_needed_ = + output_stream_->Write(static_cast(data), size); + return flush_needed_; +} + +bool OutputStreamFileWriter::WriteIoVec(std::vector* iovecs) { + DCHECK(!flushed_); + flush_needed_ = true; + if (iovecs->empty()) { + LOG(ERROR) << "no iovecs"; + flush_needed_ = false; + return false; + } + for (const WritableIoVec& iov : *iovecs) { + if (!output_stream_->Write(static_cast(iov.iov_base), + iov.iov_len)) { + flush_needed_ = false; + return false; + } + } + return true; +} + +FileOffset OutputStreamFileWriter::Seek(FileOffset offset, int whence) { + NOTREACHED(); + return -1; +} + +bool OutputStreamFileWriter::Flush() { + flush_needed_ = false; + flushed_ = true; + return output_stream_->Flush(); +} + +} // namespace crashpad diff --git a/util/file/output_stream_file_writer.h b/util/file/output_stream_file_writer.h new file mode 100644 index 00000000..6bf1c2c1 --- /dev/null +++ b/util/file/output_stream_file_writer.h @@ -0,0 +1,62 @@ +// Copyright 2020 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_FILE_OUTPUT_STREAM_FILE_WRITER_H_ +#define CRASHPAD_UTIL_FILE_OUTPUT_STREAM_FILE_WRITER_H_ + +#include + +#include "base/macros.h" +#include "util/file/file_writer.h" + +namespace crashpad { + +class OutputStreamInterface; + +//! \brief A file writer backed by a OutputSteamInterface. +//! \note The \a Seek related methods don't work and shouldn't be invoked. +class OutputStreamFileWriter : public FileWriterInterface { + public: + //! \param[in] output_stream The output stream that this object writes to. + explicit OutputStreamFileWriter( + std::unique_ptr output_stream); + ~OutputStreamFileWriter() override; + + // FileWriterInterface: + bool Write(const void* data, size_t size) override; + bool WriteIoVec(std::vector* iovecs) override; + + // FileSeekerInterface: + + //! \copydoc FileWriterInterface::Seek() + //! + //! \note This method doesn't work and shouldn't be invoked. + FileOffset Seek(FileOffset offset, int whence) override; + + //! \brief Flush data to output_stream. + //! + //! Either \a Write() or \a WriteIoVec() can't be called afterwards. + bool Flush(); + + private: + std::unique_ptr output_stream_; + bool flush_needed_; + bool flushed_; + + DISALLOW_COPY_AND_ASSIGN(OutputStreamFileWriter); +}; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_FILE_OUTPUT_STREAM_FILE_WRITER_H_ diff --git a/util/fuchsia/koid_utilities.cc b/util/fuchsia/koid_utilities.cc index 0d44f4af..f0377dc3 100644 --- a/util/fuchsia/koid_utilities.cc +++ b/util/fuchsia/koid_utilities.cc @@ -14,7 +14,10 @@ #include "util/fuchsia/koid_utilities.h" -#include +#include +#include +#include +#include #include @@ -26,62 +29,59 @@ 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(); +// Casts |handle| into a container of type T, returning a null handle if the +// actual handle type does not match that of T. +template +T CastHandle(zx::handle handle) { + zx_info_handle_basic_t actual = {}; + zx_status_t status = handle.get_info( + ZX_INFO_HANDLE_BASIC, &actual, sizeof(actual), nullptr, nullptr); + if (status != ZX_OK) { + ZX_LOG(ERROR, status) << "zx_object_get_info"; + return T(); } - return base::ScopedZxHandle(root_job); + if (actual.type != T::TYPE) { + LOG(ERROR) << "Wrong type: " << actual.type << ", expected " << T::TYPE; + return T(); + } + return T(std::move(handle)); } -bool FindProcess(const base::ScopedZxHandle& job, - zx_koid_t koid, - base::ScopedZxHandle* out) { - for (auto& proc : GetChildHandles(job.get(), ZX_INFO_JOB_PROCESSES)) { - if (GetKoidForHandle(proc.get()) == koid) { - *out = std::move(proc); - return true; - } +// Returns null handle if |koid| is not found or an error occurs. If |was_found| +// is non-null then it will be set, to distinguish not-found from other errors. +template +T GetChildHandleByKoid(const U& parent, zx_koid_t child_koid, bool* was_found) { + zx::handle handle; + zx_status_t status = + parent.get_child(child_koid, ZX_RIGHT_SAME_RIGHTS, &handle); + if (was_found) + *was_found = (status != ZX_ERR_NOT_FOUND); + if (status != ZX_OK) { + ZX_LOG(ERROR, status) << "zx_object_get_child"; + return T(); } - // TODO(scottmg): As this is recursing down the job tree all the handles are - // kept open, so this could be very expensive in terms of number of open - // handles. This function should be replaced by a syscall in the - // not-too-distant future, so hopefully OK for now. - for (const auto& child_job : - GetChildHandles(job.get(), ZX_INFO_JOB_CHILDREN)) { - if (FindProcess(child_job, koid, out)) - return true; - } - - return false; + return CastHandle(std::move(handle)); } } // namespace -std::vector GetChildKoids(zx_handle_t parent, +std::vector GetChildKoids(const zx::object_base& parent_object, zx_object_info_topic_t child_kind) { size_t actual = 0; size_t available = 0; std::vector result(100); + zx::unowned_handle parent(parent_object.get()); // This is inherently racy. Better if the process is suspended, but there's // still no guarantee that a thread isn't externally created. As a result, // must be in a retry loop. for (;;) { - zx_status_t status = zx_object_get_info(parent, - child_kind, - result.data(), - result.size() * sizeof(zx_koid_t), - &actual, - &available); + zx_status_t status = parent->get_info(child_kind, + result.data(), + result.size() * sizeof(zx_koid_t), + &actual, + &available); // If the buffer is too small (even zero), the result is still ZX_OK, not // ZX_ERR_BUFFER_TOO_SMALL. if (status != ZX_OK) { @@ -102,56 +102,40 @@ std::vector GetChildKoids(zx_handle_t parent, 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 GetThreadHandles(const zx::process& parent) { + auto koids = GetChildKoids(parent, ZX_INFO_PROCESS_THREADS); + return GetHandlesForThreadKoids(parent, koids); } -std::vector GetHandlesForChildKoids( - zx_handle_t parent, +std::vector GetHandlesForThreadKoids( + const zx::process& parent, const std::vector& koids) { - std::vector result; + std::vector result; result.reserve(koids.size()); for (zx_koid_t koid : koids) { - result.emplace_back(GetChildHandleByKoid(parent, koid)); + result.emplace_back(GetThreadHandleByKoid(parent, koid)); } return result; } -base::ScopedZxHandle GetChildHandleByKoid(zx_handle_t parent, - zx_koid_t child_koid) { - zx_handle_t handle; - zx_status_t status = - zx_object_get_child(parent, child_koid, ZX_RIGHT_SAME_RIGHTS, &handle); - if (status != ZX_OK) { - ZX_LOG(ERROR, status) << "zx_object_get_child"; - return base::ScopedZxHandle(); - } - return base::ScopedZxHandle(handle); +zx::thread GetThreadHandleByKoid(const zx::process& parent, + zx_koid_t child_koid) { + return GetChildHandleByKoid(parent, child_koid, nullptr); } -zx_koid_t GetKoidForHandle(zx_handle_t object) { +zx_koid_t GetKoidForHandle(const zx::object_base& object) { zx_info_handle_basic_t info; - zx_status_t status = zx_object_get_info( - object, ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr); + zx_status_t status = zx_object_get_info(object.get(), + ZX_INFO_HANDLE_BASIC, + &info, + sizeof(info), + nullptr, + nullptr); if (status != ZX_OK) { ZX_LOG(ERROR, status) << "zx_object_get_info"; - return ZX_HANDLE_INVALID; + return ZX_KOID_INVALID; } return info.koid; } -// TODO(scottmg): This implementation uses some debug/temporary/hacky APIs and -// ioctls that are currently the only way to go from pid to handle. This should -// hopefully eventually be replaced by more or less a single -// zx_debug_something() syscall. -base::ScopedZxHandle GetProcessFromKoid(zx_koid_t koid) { - base::ScopedZxHandle result; - if (!FindProcess(GetRootJob(), koid, &result)) { - LOG(ERROR) << "process " << koid << " not found"; - } - return result; -} - } // namespace crashpad diff --git a/util/fuchsia/koid_utilities.h b/util/fuchsia/koid_utilities.h index 5bcea105..8859bc51 100644 --- a/util/fuchsia/koid_utilities.h +++ b/util/fuchsia/koid_utilities.h @@ -15,13 +15,14 @@ #ifndef CRASHPAD_UTIL_FUCHSIA_KOID_UTILITIES_H_ #define CRASHPAD_UTIL_FUCHSIA_KOID_UTILITIES_H_ +#include +#include +#include #include #include #include -#include "base/fuchsia/scoped_zx_handle.h" - namespace crashpad { //! \brief Get a list of child koids for a parent handle. @@ -38,57 +39,43 @@ namespace crashpad { //! \return A vector of the koids representing the child objects. //! //! \sa GetChildHandles -std::vector GetChildKoids(zx_handle_t parent, +std::vector GetChildKoids(const zx::object_base& parent, zx_object_info_topic_t child_kind); //! \brief Get handles representing a list of child objects of a given parent. //! //! \param[in] parent The handle to the parent object. -//! \param[in] child_kind The type of children to retrieve from \a parent. Valid -//! values depend on the type of \a parent, but include -//! `ZX_INFO_JOB_CHILDREN` (child jobs of a job), `ZX_INFO_JOB_PROCESSES` -//! (child processes of a job), and `ZX_INFO_PROCESS_THREADS` (child threads -//! of a process). //! \return The resulting list of handles corresponding to the child objects. //! //! \sa GetChildKoids -std::vector GetChildHandles( - zx_handle_t parent, - zx_object_info_topic_t child_kind); +std::vector GetThreadHandles(const zx::process& parent); -//! \brief Convert a list of koids that are all children of a particular object -//! into handles. +//! \brief Convert a list of koids that are all children of a particular process +//! into thread handles. //! //! \param[in] parent The parent object to which the koids belong. //! \param[in] koids The list of koids. //! \return The resulting list of handles corresponding to the koids. If an //! element of \a koids is invalid or can't be retrieved, there will be a //! corresponding `ZX_HANDLE_INVALID` entry in the return. -std::vector GetHandlesForChildKoids( - zx_handle_t parent, +std::vector GetHandlesForThreadKoids( + const zx::process& parent, const std::vector& koids); -//! \brief Retrieve the child of a parent handle, based on koid. +//! \brief Retrieve the handle of a process' thread, based on koid. //! //! \param[in] parent The parent object to which the child belongs. //! \param[in] child_koid The koid of the child to retrieve. //! \return A handle representing \a child_koid, or `ZX_HANDLE_INVALID` if the //! handle could not be retrieved, in which case an error will be logged. -base::ScopedZxHandle GetChildHandleByKoid(zx_handle_t parent, - zx_koid_t child_koid); - -//! \brief Gets a process handle given the process' koid. -//! -//! \param[in] koid The process id. -//! \return A zx_handle_t (owned by a base::ScopedZxHandle) for the process. If -//! the handle is invalid, an error will have been logged. -base::ScopedZxHandle GetProcessFromKoid(zx_koid_t koid); +zx::thread GetThreadHandleByKoid(const zx::process& parent, + zx_koid_t child_koid); //! \brief Retrieves the koid for a given object handle. //! //! \param[in] object The handle for which the koid is to be retrieved. //! \return The koid of \a handle, or `ZX_HANDLE_INVALID` with an error logged. -zx_koid_t GetKoidForHandle(zx_handle_t object); +zx_koid_t GetKoidForHandle(const zx::object_base& object); } // namespace crashpad diff --git a/util/fuchsia/scoped_task_suspend.cc b/util/fuchsia/scoped_task_suspend.cc index ca0fd564..8591d558 100644 --- a/util/fuchsia/scoped_task_suspend.cc +++ b/util/fuchsia/scoped_task_suspend.cc @@ -14,68 +14,55 @@ #include "util/fuchsia/scoped_task_suspend.h" -#include -#include -#include +#include +#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 { +ScopedTaskSuspend::ScopedTaskSuspend(const zx::process& process) { + DCHECK_NE(process.get(), zx::process::self()->get()); -zx_obj_type_t GetHandleType(zx_handle_t handle) { - zx_info_handle_basic_t basic; - zx_status_t status = zx_object_get_info( - handle, ZX_INFO_HANDLE_BASIC, &basic, sizeof(basic), nullptr, nullptr); - if (status != ZX_OK) { - ZX_LOG(ERROR, status) << "zx_object_get_info"; - return ZX_OBJ_TYPE_NONE; - } - return basic.type; -} - -// Returns the suspend token of the suspended thread. This function attempts -// to wait a short time for the thread to actually suspend before returning -// but this is not guaranteed. -base::ScopedZxHandle SuspendThread(zx_handle_t thread) { - zx_handle_t token = ZX_HANDLE_INVALID; - zx_status_t status = zx_task_suspend_token(thread, &token); - if (status != ZX_OK) { - ZX_LOG(ERROR, status) << "zx_task_suspend"; - base::ScopedZxHandle(); + const zx_status_t suspend_status = process.suspend(&suspend_token_); + if (suspend_status != ZX_OK) { + ZX_LOG(ERROR, suspend_status) << "zx_task_suspend"; + return; } - zx_signals_t observed = 0u; - if (zx_object_wait_one(thread, ZX_THREAD_SUSPENDED, - zx_deadline_after(ZX_MSEC(50)), &observed) != ZX_OK) { - LOG(ERROR) << "thread failed to suspend"; - } - return base::ScopedZxHandle(token); -} + // suspend() is asynchronous so we now check that each thread is indeed + // suspended, up to some deadline. + for (const auto& thread : GetThreadHandles(process)) { + // We omit the crashed thread (blocked in an exception) as it is technically + // not suspended, cf. ZX-3772. + zx_info_thread_t info; + if (thread.get_info( + ZX_INFO_THREAD, &info, sizeof(info), nullptr, nullptr) == ZX_OK) { + if (info.state == ZX_THREAD_STATE_BLOCKED_EXCEPTION) { + continue; + } + } -} // namespace - -ScopedTaskSuspend::ScopedTaskSuspend(zx_handle_t task) { - DCHECK_NE(task, zx_process_self()); - DCHECK_NE(task, zx_thread_self()); - - zx_obj_type_t type = GetHandleType(task); - if (type == ZX_OBJ_TYPE_THREAD) { - suspend_tokens_.push_back(SuspendThread(task)); - } else if (type == ZX_OBJ_TYPE_PROCESS) { - for (const auto& thread : GetChildHandles(task, ZX_INFO_PROCESS_THREADS)) - suspend_tokens_.push_back(SuspendThread(thread.get())); - } else { - LOG(ERROR) << "unexpected handle type"; + zx_signals_t observed = 0u; + const zx_status_t wait_status = thread.wait_one( + ZX_THREAD_SUSPENDED, zx::deadline_after(zx::msec(50)), &observed); + if (wait_status != ZX_OK) { + zx_info_thread_t info = {}; + zx_status_t info_status = thread.get_info( + ZX_INFO_THREAD, &info, sizeof(info), nullptr, nullptr); + ZX_LOG(ERROR, wait_status) << "thread failed to suspend"; + LOG(ERROR) << "Thread info status " << info_status; + if (info_status == ZX_OK) { + LOG(ERROR) << "Thread state " << info.state; + } + } } } -ScopedTaskSuspend::~ScopedTaskSuspend() = default; - } // namespace crashpad diff --git a/util/fuchsia/scoped_task_suspend.h b/util/fuchsia/scoped_task_suspend.h index f817364d..afe497bc 100644 --- a/util/fuchsia/scoped_task_suspend.h +++ b/util/fuchsia/scoped_task_suspend.h @@ -15,37 +15,31 @@ #ifndef CRASHPAD_UTIL_FUCHSIA_SCOPED_TASK_SUSPEND_H_ #define CRASHPAD_UTIL_FUCHSIA_SCOPED_TASK_SUSPEND_H_ -#include +#include +#include +#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. +//! Suspending a process is asynchronous, and may take an arbitrary amount of +//! time. As a result, this class is limited to being a best-effort, and +//! correct suspension/resumption cannot be relied upon. //! //! Callers should not attempt to suspend the current task as obtained via //! `zx_process_self()`. class ScopedTaskSuspend { public: - explicit ScopedTaskSuspend(zx_handle_t task); - ~ScopedTaskSuspend(); + explicit ScopedTaskSuspend(const zx::process& process); + ~ScopedTaskSuspend() = default; private: - // Could be one (for a thread) or many (for every process in a thread). - std::vector suspend_tokens_; + zx::suspend_token suspend_token_; DISALLOW_COPY_AND_ASSIGN(ScopedTaskSuspend); }; diff --git a/util/ios/exception_processor.h b/util/ios/exception_processor.h new file mode 100644 index 00000000..76a8c8b3 --- /dev/null +++ b/util/ios/exception_processor.h @@ -0,0 +1,37 @@ +// Copyright 2020 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_IOS_EXCEPTION_PROCESSOR_H_ +#define CRASHPAD_UTIL_IOS_EXCEPTION_PROCESSOR_H_ + +namespace crashpad { + +//! \brief Installs the Objective-C exception preprocessor. +//! +//! When code raises an Objective-C exception, unwind the stack looking for +//! any exception handlers. If an exception handler is encountered, test to +//! see if it is a function known to be a catch-and-rethrow 'sinkhole' exception +//! handler. Various routines in UIKit do this, and they obscure the +//! crashing stack, since the original throw location is no longer present +//! on the stack (just the re-throw) when Crashpad captures the crash +//! report. In the case of sinkholes, trigger an immediate exception to +//! capture the original stack. +//! +//! This should be installed at the same time the CrashpadClient installs the +//! signal handler. It should only be installed once. +void InstallObjcExceptionPreprocessor(); + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_IOS_EXCEPTION_PROCESSOR_H_ diff --git a/util/ios/exception_processor.mm b/util/ios/exception_processor.mm new file mode 100644 index 00000000..139a8516 --- /dev/null +++ b/util/ios/exception_processor.mm @@ -0,0 +1,337 @@ +// Copyright 2020 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/ios/exception_processor.h" + +#import +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "base/bit_cast.h" +#include "base/logging.h" +#include "base/strings/sys_string_conversions.h" +#include "build/build_config.h" + +namespace { + +// From 10.15.0 objc4-779.1/runtime/objc-exception.mm. +struct objc_typeinfo { + const void* const* vtable; + const char* name; + Class cls_unremapped; +}; +struct objc_exception { + id obj; + objc_typeinfo tinfo; +}; + +// From 10.15.0 objc4-779.1/runtime/objc-abi.h. +extern "C" const void* const objc_ehtype_vtable[]; + +// https://github.com/llvm/llvm-project/blob/09dc884eb2e4/libcxxabi/src/cxa_exception.h +static const uint64_t kOurExceptionClass = 0x434c4e47432b2b00; +struct __cxa_exception { +#if defined(ARCH_CPU_64_BITS) + void* reserve; + size_t referenceCount; +#endif + std::type_info* exceptionType; + void (*exceptionDestructor)(void*); + std::unexpected_handler unexpectedHandler; + std::terminate_handler terminateHandler; + __cxa_exception* nextException; + int handlerCount; + int handlerSwitchValue; + const unsigned char* actionRecord; + const unsigned char* languageSpecificData; + void* catchTemp; + void* adjustedPtr; +#if !defined(ARCH_CPU_64_BITS) + size_t referenceCount; +#endif + _Unwind_Exception unwindHeader; +}; + +objc_exception_preprocessor g_next_preprocessor; +bool g_exception_preprocessor_installed; + +void TerminatingFromUncaughtNSException(id exception, const char* sinkhole) { + // TODO(justincohen): This is incomplete, as the signal handler will not have + // access to the exception name and reason. Pass that along somehow here. + NSString* exception_message_ns = [NSString + stringWithFormat:@"%@: %@", [exception name], [exception reason]]; + std::string exception_message = base::SysNSStringToUTF8(exception_message_ns); + LOG(INFO) << "Terminating from Objective-C exception: " << exception_message + << " with sinkhole: " << sinkhole; + // TODO(justincohen): This is temporary, as crashpad can capture this + // exception directly instead. + std::terminate(); +} + +// Returns true if |path| equals |sinkhole| on device. Simulator paths prepend +// much of Xcode's internal structure, so check that |path| ends with |sinkhole| +// for simulator. +bool ModulePathMatchesSinkhole(const char* path, const char* sinkhole) { +#if TARGET_OS_SIMULATOR + size_t path_length = strlen(path); + size_t sinkhole_length = strlen(sinkhole); + if (sinkhole_length > path_length) + return false; + return strncmp(path + path_length - sinkhole_length, + sinkhole, + sinkhole_length) == 0; +#else + return strcmp(path, sinkhole) == 0; +#endif +} + +int LoggingUnwStep(unw_cursor_t* cursor) { + int rv = unw_step(cursor); + if (rv < 0) { + LOG(ERROR) << "unw_step: " << rv; + } + return rv; +} + +id ObjcExceptionPreprocessor(id exception) { + // Unwind the stack looking for any exception handlers. If an exception + // handler is encountered, test to see if it is a function known to catch- + // and-rethrow as a "top-level" exception handler. Various routines in + // Cocoa/UIKit do this, and it obscures the crashing stack, since the original + // throw location is no longer present on the stack (just the re-throw) when + // Crashpad captures the crash report. + unw_context_t context; + unw_getcontext(&context); + + unw_cursor_t cursor; + unw_init_local(&cursor, &context); + + static const void* this_base_address = []() -> const void* { + Dl_info dl_info; + if (!dladdr(reinterpret_cast(&ObjcExceptionPreprocessor), + &dl_info)) { + LOG(ERROR) << "dladdr: " << dlerror(); + return nullptr; + } + return dl_info.dli_fbase; + }(); + + // Generate an exception_header for the __personality_routine. + // From 10.15.0 objc4-779.1/runtime/objc-exception.mm objc_exception_throw. + objc_exception* exception_objc = reinterpret_cast( + __cxxabiv1::__cxa_allocate_exception(sizeof(objc_exception))); + exception_objc->obj = exception; + exception_objc->tinfo.vtable = objc_ehtype_vtable + 2; + exception_objc->tinfo.name = object_getClassName(exception); + exception_objc->tinfo.cls_unremapped = object_getClass(exception); + + // https://github.com/llvm/llvm-project/blob/c5d2746fbea7/libcxxabi/src/cxa_exception.cpp + // __cxa_throw + __cxa_exception* exception_header = + reinterpret_cast<__cxa_exception*>(exception_objc) - 1; + exception_header->unexpectedHandler = std::get_unexpected(); + exception_header->terminateHandler = std::get_terminate(); + exception_header->exceptionType = + reinterpret_cast(&exception_objc->tinfo); + exception_header->unwindHeader.exception_class = kOurExceptionClass; + + bool handler_found = false; + while (LoggingUnwStep(&cursor) > 0) { + unw_proc_info_t frame_info; + if (unw_get_proc_info(&cursor, &frame_info) != UNW_ESUCCESS) { + continue; + } + + if (frame_info.handler == 0) { + continue; + } + + // Check to see if the handler is really an exception handler. + __personality_routine p = + reinterpret_cast<__personality_routine>(frame_info.handler); + + // From 10.15.0 libunwind-35.4/src/UnwindLevel1.c. + _Unwind_Reason_Code personalityResult = (*p)( + 1, + _UA_SEARCH_PHASE, + exception_header->unwindHeader.exception_class, + reinterpret_cast<_Unwind_Exception*>(&exception_header->unwindHeader), + reinterpret_cast<_Unwind_Context*>(&cursor)); + switch (personalityResult) { + case _URC_HANDLER_FOUND: + break; + case _URC_CONTINUE_UNWIND: + continue; + default: + break; + } + + char proc_name[512]; + unw_word_t offset; + if (unw_get_proc_name(&cursor, proc_name, sizeof(proc_name), &offset) != + UNW_ESUCCESS) { + // The symbol has no name, so see if it belongs to the same image as + // this function. + Dl_info dl_info; + if (dladdr(reinterpret_cast(frame_info.start_ip), + &dl_info)) { + if (dl_info.dli_fbase == this_base_address) { + // This is a handler in our image, so allow it to run. + handler_found = true; + break; + } + } + + // This handler does not belong to us, so continue the search. + continue; + } + + // Check if the function is one that is known to obscure (by way of + // catch-and-rethrow) exception stack traces. If it is, sinkhole it + // by crashing here at the point of throw. + constexpr const char* kExceptionSymbolNameSinkholes[] = { + // The two CF symbol names will also be captured by the CoreFoundation + // library path check below, but for completeness they are listed here, + // since they appear unredacted. + "CFRunLoopRunSpecific", + "_CFXNotificationPost", + "__NSFireDelayedPerform", + }; + for (const char* sinkhole : kExceptionSymbolNameSinkholes) { + if (strcmp(sinkhole, proc_name) == 0) { + TerminatingFromUncaughtNSException(exception, sinkhole); + } + } + + // On iOS, function names are often reported as "", although they + // do appear when attached to the debugger. When this happens, use the path + // of the image to determine if the handler is an exception sinkhole. + constexpr const char* kExceptionLibraryPathSinkholes[] = { + // Everything in this library is a sinkhole, specifically + // _dispatch_client_callout. Both are needed here depending on whether + // the debugger is attached (introspection only appears when a simulator + // is attached to a debugger). + "/usr/lib/system/introspection/libdispatch.dylib", + "/usr/lib/system/libdispatch.dylib", + + // __CFRunLoopDoTimers and __CFRunLoopRun are sinkholes. Consider also + // checking that a few frames up is CFRunLoopRunSpecific(). + "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation", + }; + + Dl_info dl_info; + if (dladdr(reinterpret_cast(frame_info.start_ip), &dl_info) != + 0) { + for (const char* sinkhole : kExceptionLibraryPathSinkholes) { + if (ModulePathMatchesSinkhole(dl_info.dli_fname, sinkhole)) { + TerminatingFromUncaughtNSException(exception, sinkhole); + } + } + } + + // Some sinkholes are harder to find. _UIGestureEnvironmentUpdate + // in UIKitCore is an example. UIKitCore can't be added to + // kExceptionLibraryPathSinkholes because it uses Objective-C exceptions + // internally and also has has non-sinkhole handlers. Since + // _UIGestureEnvironmentUpdate is always called from + // -[UIGestureEnvironment _deliverEvent:toGestureRecognizers:usingBlock:], + // inspect the caller frame info to match the sinkhole. + constexpr const char* kUIKitCorePath = + "/System/Library/PrivateFrameworks/UIKitCore.framework/UIKitCore"; + if (ModulePathMatchesSinkhole(dl_info.dli_fname, kUIKitCorePath)) { + unw_proc_info_t caller_frame_info; + if (LoggingUnwStep(&cursor) > 0 && + unw_get_proc_info(&cursor, &caller_frame_info) == UNW_ESUCCESS) { + static IMP uigesture_deliver_event_imp = [] { + IMP imp = class_getMethodImplementation( + NSClassFromString(@"UIGestureEnvironment"), + NSSelectorFromString( + @"_deliverEvent:toGestureRecognizers:usingBlock:")); + + // From 10.15.0 objc4-779.1/runtime/objc-class.mm + // class_getMethodImplementation returns nil or _objc_msgForward on + // failure. + if (!imp || imp == _objc_msgForward) { + LOG(WARNING) << "Unable to find -[UIGestureEnvironment " + "_deliverEvent:toGestureRecognizers:usingBlock:]"; + return bit_cast(nullptr); // IMP is a function pointer type. + } + return imp; + }(); + + if (uigesture_deliver_event_imp == + reinterpret_cast(caller_frame_info.start_ip)) { + TerminatingFromUncaughtNSException(exception, + "_UIGestureEnvironmentUpdate"); + } + } + } + + handler_found = true; + + break; + } + + // If no handler is found, __cxa_throw would call failed_throw and terminate. + // See: + // https://github.com/llvm/llvm-project/blob/c5d2746fbea7/libcxxabi/src/cxa_exception.cpp + // __cxa_throw. Instead, terminate via TerminatingFromUncaughtNSException so + // the exception name and reason are properly recorded. + if (!handler_found) { + TerminatingFromUncaughtNSException(exception, "__cxa_throw"); + } + + // Forward to the next preprocessor. + if (g_next_preprocessor) + return g_next_preprocessor(exception); + + return exception; +} + +} // namespace + +namespace crashpad { + +void InstallObjcExceptionPreprocessor() { + DCHECK(!g_exception_preprocessor_installed); + + g_next_preprocessor = + objc_setExceptionPreprocessor(&ObjcExceptionPreprocessor); + g_exception_preprocessor_installed = true; +} + +void UninstallObjcExceptionPreprocessor() { + DCHECK(g_exception_preprocessor_installed); + + objc_setExceptionPreprocessor(g_next_preprocessor); + g_next_preprocessor = nullptr; + g_exception_preprocessor_installed = false; +} + +} // namespace crashpad diff --git a/util/ios/exception_processor_test.mm b/util/ios/exception_processor_test.mm new file mode 100644 index 00000000..c9aa7169 --- /dev/null +++ b/util/ios/exception_processor_test.mm @@ -0,0 +1,41 @@ +// Copyright 2020 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import +#include +#include + +#include "gtest/gtest.h" +#include "testing/platform_test.h" + +namespace crashpad { +namespace test { +namespace { + +using IOSExceptionProcessor = PlatformTest; + +TEST_F(IOSExceptionProcessor, SelectorExists) { + IMP uigesture_deliver_event_imp = class_getMethodImplementation( + NSClassFromString(@"UIGestureEnvironment"), + NSSelectorFromString(@"_deliverEvent:toGestureRecognizers:usingBlock:")); + + // From 10.15.0 objc4-779.1/runtime/objc-class.mm + // class_getMethodImplementation returns nil or _objc_msgForward on failure. + ASSERT_TRUE(uigesture_deliver_event_imp); + ASSERT_NE(uigesture_deliver_event_imp, _objc_msgForward); +} + +} // namespace +} // namespace test +} // namespace crashpad diff --git a/util/ios/ios_system_data_collector.h b/util/ios/ios_system_data_collector.h new file mode 100644 index 00000000..45837c66 --- /dev/null +++ b/util/ios/ios_system_data_collector.h @@ -0,0 +1,81 @@ +// Copyright 2020 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_IOS_IOS_SYSTEM_DATA_COLLECTOR_H_ +#define CRASHPAD_UTIL_IOS_IOS_SYSTEM_DATA_COLLECTOR_H_ + +#import + +#include + +namespace crashpad { + +//! \brief Used to collect system level data before a crash occurs. +class IOSSystemDataCollector { + public: + IOSSystemDataCollector(); + ~IOSSystemDataCollector(); + + void OSVersion(int* major, int* minor, int* bugfix, std::string* build) const; + std::string MachineDescription() const { return machine_description_; } + int ProcessorCount() const { return processor_count_; } + std::string CPUVendor() const { return cpu_vendor_; } + bool HasDaylightSavingTime() const { return has_next_daylight_saving_time_; } + bool IsDaylightSavingTime() const { return is_daylight_saving_time_; } + int StandardOffsetSeconds() const { return standard_offset_seconds_; } + int DaylightOffsetSeconds() const { return daylight_offset_seconds_; } + std::string StandardName() const { return standard_name_; } + std::string DaylightName() const { return daylight_name_; } + + // Currently unused by minidump. + int Orientation() const { return orientation_; } + + private: + // Notification handlers. + void InstallHandlers(); + static void SystemTimeZoneDidChangeNotificationHandler( + CFNotificationCenterRef center, + void* observer, + CFStringRef name, + const void* object, + CFDictionaryRef userInfo); + void SystemTimeZoneDidChangeNotification(); + + static void OrientationDidChangeNotificationHandler( + CFNotificationCenterRef center, + void* observer, + CFStringRef name, + const void* object, + CFDictionaryRef userInfo); + void OrientationDidChangeNotification(); + + int major_version_; + int minor_version_; + int patch_version_; + std::string build_; + std::string machine_description_; + int orientation_; + int processor_count_; + std::string cpu_vendor_; + bool has_next_daylight_saving_time_; + bool is_daylight_saving_time_; + int standard_offset_seconds_; + int daylight_offset_seconds_; + std::string standard_name_; + std::string daylight_name_; +}; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_IOS_IOS_SYSTEM_DATA_COLLECTOR_H_ diff --git a/util/ios/ios_system_data_collector.mm b/util/ios/ios_system_data_collector.mm new file mode 100644 index 00000000..c81fe3f8 --- /dev/null +++ b/util/ios/ios_system_data_collector.mm @@ -0,0 +1,209 @@ +// Copyright 2020 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/ios/ios_system_data_collector.h" + +#include +#include + +#import +#include +#import + +#include "base/mac/mach_logging.h" +#include "base/numerics/safe_conversions.h" +#include "base/strings/sys_string_conversions.h" +#include "build/build_config.h" + +namespace { + +std::string ReadStringSysctlByName(const char* name) { + size_t buf_len; + if (sysctlbyname(name, nullptr, &buf_len, nullptr, 0) != 0) { + PLOG(WARNING) << "sysctlbyname (size) " << name; + return std::string(); + } + + if (buf_len == 0) { + return std::string(); + } + + std::string value(buf_len - 1, '\0'); + if (sysctlbyname(name, &value[0], &buf_len, nullptr, 0) != 0) { + PLOG(WARNING) << "sysctlbyname " << name; + return std::string(); + } + + return value; +} + +} // namespace + +namespace crashpad { + +IOSSystemDataCollector::IOSSystemDataCollector() + : major_version_(0), + minor_version_(0), + patch_version_(0), + build_(), + machine_description_(), + orientation_(0), + processor_count_(0), + cpu_vendor_(), + has_next_daylight_saving_time_(false), + is_daylight_saving_time_(false), + standard_offset_seconds_(0), + daylight_offset_seconds_(0), + standard_name_(), + daylight_name_() { + NSOperatingSystemVersion version = + [[NSProcessInfo processInfo] operatingSystemVersion]; + major_version_ = base::saturated_cast(version.majorVersion); + minor_version_ = base::saturated_cast(version.minorVersion); + patch_version_ = base::saturated_cast(version.patchVersion); + processor_count_ = + base::saturated_cast([[NSProcessInfo processInfo] processorCount]); + build_ = ReadStringSysctlByName("kern.osversion"); + +#if defined(ARCH_CPU_X86_64) + cpu_vendor_ = ReadStringSysctlByName("machdep.cpu.vendor"); +#endif + +#if TARGET_OS_SIMULATOR + // TODO(justincohen): Consider adding board and model information to + // |machine_description| as well (similar to MacModelAndBoard in + // util/mac/mac_util.cc). + switch (UI_USER_INTERFACE_IDIOM()) { + case UIUserInterfaceIdiomPhone: + machine_description_ = "iOS Simulator (iPhone)"; + break; + case UIUserInterfaceIdiomPad: + machine_description_ = "iOS Simulator (iPad)"; + break; + default: + machine_description_ = "iOS Simulator (Unknown)"; + break; + } +#elif TARGET_OS_IPHONE + utsname uts; + if (uname(&uts) == 0) { + machine_description_ = uts.machine; + } +#else +#error "Unexpected target type OS." +#endif + + InstallHandlers(); +} + +IOSSystemDataCollector::~IOSSystemDataCollector() { + CFNotificationCenterRemoveEveryObserver(CFNotificationCenterGetLocalCenter(), + this); +} + +void IOSSystemDataCollector::OSVersion(int* major, + int* minor, + int* bugfix, + std::string* build) const { + *major = major_version_; + *minor = minor_version_; + *bugfix = patch_version_; + build->assign(build_); +} + +void IOSSystemDataCollector::InstallHandlers() { + // Timezone. + CFNotificationCenterAddObserver( + CFNotificationCenterGetLocalCenter(), + this, + IOSSystemDataCollector::SystemTimeZoneDidChangeNotificationHandler, + reinterpret_cast(NSSystemTimeZoneDidChangeNotification), + nullptr, + CFNotificationSuspensionBehaviorDeliverImmediately); + SystemTimeZoneDidChangeNotification(); + + // Orientation. + CFNotificationCenterAddObserver( + CFNotificationCenterGetLocalCenter(), + this, + IOSSystemDataCollector::OrientationDidChangeNotificationHandler, + reinterpret_cast(UIDeviceOrientationDidChangeNotification), + nullptr, + CFNotificationSuspensionBehaviorDeliverImmediately); + OrientationDidChangeNotification(); +} + +// static +void IOSSystemDataCollector::SystemTimeZoneDidChangeNotificationHandler( + CFNotificationCenterRef center, + void* observer, + CFStringRef name, + const void* object, + CFDictionaryRef userInfo) { + static_cast(observer) + ->SystemTimeZoneDidChangeNotification(); +} + +void IOSSystemDataCollector::SystemTimeZoneDidChangeNotification() { + NSTimeZone* time_zone = NSTimeZone.localTimeZone; + NSDate* transition = + [time_zone nextDaylightSavingTimeTransitionAfterDate:[NSDate date]]; + if (transition == nil) { + has_next_daylight_saving_time_ = false; + is_daylight_saving_time_ = false; + standard_offset_seconds_ = + base::saturated_cast([time_zone secondsFromGMTForDate:transition]); + standard_name_ = base::SysNSStringToUTF8([time_zone abbreviation]); + daylight_offset_seconds_ = standard_offset_seconds_; + daylight_name_ = standard_name_; + } else { + has_next_daylight_saving_time_ = true; + is_daylight_saving_time_ = time_zone.isDaylightSavingTime; + if (time_zone.isDaylightSavingTime) { + standard_offset_seconds_ = base::saturated_cast( + [time_zone secondsFromGMTForDate:transition]); + standard_name_ = + base::SysNSStringToUTF8([time_zone abbreviationForDate:transition]); + daylight_offset_seconds_ = + base::saturated_cast([time_zone secondsFromGMT]); + daylight_name_ = base::SysNSStringToUTF8([time_zone abbreviation]); + } else { + standard_offset_seconds_ = + base::saturated_cast([time_zone secondsFromGMT]); + standard_name_ = base::SysNSStringToUTF8([time_zone abbreviation]); + daylight_offset_seconds_ = base::saturated_cast( + [time_zone secondsFromGMTForDate:transition]); + daylight_name_ = + base::SysNSStringToUTF8([time_zone abbreviationForDate:transition]); + } + } +} + +// static +void IOSSystemDataCollector::OrientationDidChangeNotificationHandler( + CFNotificationCenterRef center, + void* observer, + CFStringRef name, + const void* object, + CFDictionaryRef userInfo) { + static_cast(observer) + ->OrientationDidChangeNotification(); +} + +void IOSSystemDataCollector::OrientationDidChangeNotification() { + orientation_ = + base::saturated_cast([[UIDevice currentDevice] orientation]); +} + +} // namespace crashpad diff --git a/util/linux/auxiliary_vector_test.cc b/util/linux/auxiliary_vector_test.cc index d4cfcb74..00af5abe 100644 --- a/util/linux/auxiliary_vector_test.cc +++ b/util/linux/auxiliary_vector_test.cc @@ -120,8 +120,9 @@ void TestAgainstCloneOrSelf(pid_t pid) { #if defined(AT_SYSINFO_EHDR) LinuxVMAddress vdso_addr; - ASSERT_TRUE(aux.GetValue(AT_SYSINFO_EHDR, &vdso_addr)); - EXPECT_TRUE(mappings.FindMapping(vdso_addr)); + if (aux.GetValue(AT_SYSINFO_EHDR, &vdso_addr)) { + EXPECT_TRUE(mappings.FindMapping(vdso_addr)); + } #endif // AT_SYSINFO_EHDR #if defined(AT_EXECFN) diff --git a/util/linux/direct_ptrace_connection.cc b/util/linux/direct_ptrace_connection.cc index a3df425f..8b552055 100644 --- a/util/linux/direct_ptrace_connection.cc +++ b/util/linux/direct_ptrace_connection.cc @@ -17,6 +17,7 @@ #include #include "util/file/file_io.h" +#include "util/linux/proc_task_reader.h" namespace crashpad { @@ -81,4 +82,9 @@ ProcessMemory* DirectPtraceConnection::Memory() { return &memory_; } +bool DirectPtraceConnection::Threads(std::vector* threads) { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return ReadThreadIDs(pid_, threads); +} + } // namespace crashpad diff --git a/util/linux/direct_ptrace_connection.h b/util/linux/direct_ptrace_connection.h index 53594ef9..10f8370c 100644 --- a/util/linux/direct_ptrace_connection.h +++ b/util/linux/direct_ptrace_connection.h @@ -56,6 +56,7 @@ class DirectPtraceConnection : public PtraceConnection { bool ReadFileContents(const base::FilePath& path, std::string* contents) override; ProcessMemory* Memory() override; + bool Threads(std::vector* threads) override; private: std::vector> attachments_; diff --git a/util/linux/exception_handler_client.cc b/util/linux/exception_handler_client.cc index 6333351c..98edb306 100644 --- a/util/linux/exception_handler_client.cc +++ b/util/linux/exception_handler_client.cc @@ -15,27 +15,84 @@ #include "util/linux/exception_handler_client.h" #include +#include #include -#include #include #include #include "base/logging.h" #include "base/posix/eintr_wrapper.h" #include "build/build_config.h" +#include "third_party/lss/lss.h" #include "util/file/file_io.h" #include "util/linux/ptrace_broker.h" +#include "util/linux/socket.h" +#include "util/misc/from_pointer_cast.h" #include "util/posix/signals.h" +#if defined(OS_ANDROID) +#include +#endif + namespace crashpad { -ExceptionHandlerClient::ExceptionHandlerClient(int sock) - : server_sock_(sock), ptracer_(-1), can_set_ptracer_(true) {} +namespace { + +class ScopedSigprocmaskRestore { + public: + explicit ScopedSigprocmaskRestore(const kernel_sigset_t& set_to_block) + : orig_mask_(), mask_is_set_(false) { + mask_is_set_ = sys_sigprocmask(SIG_BLOCK, &set_to_block, &orig_mask_) == 0; + DPLOG_IF(ERROR, !mask_is_set_) << "sigprocmask"; + } + + ~ScopedSigprocmaskRestore() { + if (mask_is_set_ && + sys_sigprocmask(SIG_SETMASK, &orig_mask_, nullptr) != 0) { + DPLOG(ERROR) << "sigprocmask"; + } + } + + private: + kernel_sigset_t orig_mask_; + bool mask_is_set_; + + DISALLOW_COPY_AND_ASSIGN(ScopedSigprocmaskRestore); +}; + +} // namespace + +ExceptionHandlerClient::ExceptionHandlerClient(int sock, bool multiple_clients) + : server_sock_(sock), + ptracer_(-1), + can_set_ptracer_(true), + multiple_clients_(multiple_clients) {} ExceptionHandlerClient::~ExceptionHandlerClient() = default; -int ExceptionHandlerClient::RequestCrashDump(const ClientInformation& info) { - int status = SendCrashDumpRequest(info); +bool ExceptionHandlerClient::GetHandlerCredentials(ucred* creds) { + ExceptionHandlerProtocol::ClientToServerMessage message = {}; + message.type = + ExceptionHandlerProtocol::ClientToServerMessage::kTypeCheckCredentials; + if (UnixCredentialSocket::SendMsg(server_sock_, &message, sizeof(message)) != + 0) { + return false; + } + + ExceptionHandlerProtocol::ServerToClientMessage response; + return UnixCredentialSocket::RecvMsg( + server_sock_, &response, sizeof(response), creds); +} + +int ExceptionHandlerClient::RequestCrashDump( + const ExceptionHandlerProtocol::ClientInformation& info) { + VMAddress sp = FromPointerCast(&sp); + + if (multiple_clients_) { + return SignalCrashDump(info, sp); + } + + int status = SendCrashDumpRequest(info, sp); if (status != 0) { return status; } @@ -61,59 +118,56 @@ 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; +int ExceptionHandlerClient::SignalCrashDump( + const ExceptionHandlerProtocol::ClientInformation& info, + VMAddress stack_pointer) { + kernel_sigset_t dump_done_sigset; + sys_sigemptyset(&dump_done_sigset); + sys_sigaddset(&dump_done_sigset, ExceptionHandlerProtocol::kDumpDoneSignal); + ScopedSigprocmaskRestore scoped_block(dump_done_sigset); - iovec iov; - iov.iov_base = &message; - iov.iov_len = sizeof(message); + int status = SendCrashDumpRequest(info, stack_pointer); + if (status != 0) { + return status; + } - msghdr msg; - msg.msg_name = nullptr; - msg.msg_namelen = 0; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - ucred creds; - creds.pid = getpid(); - creds.uid = geteuid(); - creds.gid = getegid(); - - char cmsg_buf[CMSG_SPACE(sizeof(creds))]; - msg.msg_control = cmsg_buf; - msg.msg_controllen = sizeof(cmsg_buf); - - cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); - cmsg->cmsg_level = SOL_SOCKET; - cmsg->cmsg_type = SCM_CREDENTIALS; - cmsg->cmsg_len = CMSG_LEN(sizeof(creds)); - *reinterpret_cast(CMSG_DATA(cmsg)) = creds; - - if (HANDLE_EINTR(sendmsg(server_sock_, &msg, MSG_NOSIGNAL)) < 0) { - PLOG(ERROR) << "sendmsg"; + siginfo_t siginfo = {}; + timespec timeout; + timeout.tv_sec = 5; + timeout.tv_nsec = 0; + if (HANDLE_EINTR(sys_sigtimedwait(&dump_done_sigset, &siginfo, &timeout)) < + 0) { return errno; } return 0; } +int ExceptionHandlerClient::SendCrashDumpRequest( + const ExceptionHandlerProtocol::ClientInformation& info, + VMAddress stack_pointer) { + ExceptionHandlerProtocol::ClientToServerMessage message; + message.type = + ExceptionHandlerProtocol::ClientToServerMessage::kTypeCrashDumpRequest; + message.requesting_thread_stack_address = stack_pointer; + message.client_info = info; + return UnixCredentialSocket::SendMsg(server_sock_, &message, sizeof(message)); +} + int ExceptionHandlerClient::WaitForCrashDumpComplete() { - ServerToClientMessage message; + ExceptionHandlerProtocol::ServerToClientMessage message; // If the server hangs up, ReadFileExactly will return false without setting // errno. errno = 0; while (ReadFileExactly(server_sock_, &message, sizeof(message))) { switch (message.type) { - case ServerToClientMessage::kTypeForkBroker: { + case ExceptionHandlerProtocol::ServerToClientMessage::kTypeForkBroker: { Signals::InstallDefaultHandler(SIGCHLD); pid_t pid = fork(); if (pid <= 0) { - Errno error = pid < 0 ? errno : 0; + ExceptionHandlerProtocol::Errno error = pid < 0 ? errno : 0; if (!WriteFile(server_sock_, &error, sizeof(error))) { return errno; } @@ -144,16 +198,22 @@ int ExceptionHandlerClient::WaitForCrashDumpComplete() { continue; } - case ServerToClientMessage::kTypeSetPtracer: { - Errno result = SetPtracer(message.pid); + case ExceptionHandlerProtocol::ServerToClientMessage::kTypeSetPtracer: { + ExceptionHandlerProtocol::Errno result = SetPtracer(message.pid); if (!WriteFile(server_sock_, &result, sizeof(result))) { return errno; } continue; } - case ServerToClientMessage::kTypeCrashDumpComplete: - case ServerToClientMessage::kTypeCrashDumpFailed: + case ExceptionHandlerProtocol::ServerToClientMessage::kTypeCredentials: + DCHECK(false); + continue; + + case ExceptionHandlerProtocol::ServerToClientMessage:: + kTypeCrashDumpComplete: + case ExceptionHandlerProtocol::ServerToClientMessage:: + kTypeCrashDumpFailed: return 0; } diff --git a/util/linux/exception_handler_client.h b/util/linux/exception_handler_client.h index a60b0656..4e10fa62 100644 --- a/util/linux/exception_handler_client.h +++ b/util/linux/exception_handler_client.h @@ -15,6 +15,7 @@ #ifndef CRASHPAD_UTIL_LINUX_EXCEPTION_HANDLER_CLIENT_H_ #define CRASHPAD_UTIL_LINUX_EXCEPTION_HANDLER_CLIENT_H_ +#include #include #include "base/macros.h" @@ -28,17 +29,30 @@ class ExceptionHandlerClient { //! \brief Constructs this object. //! //! \param[in] sock A socket connected to an ExceptionHandlerServer. - explicit ExceptionHandlerClient(int sock); + //! \param[in] multiple_clients `true` if this socket may be used by multiple + //! clients. + ExceptionHandlerClient(int sock, bool multiple_clients); ~ExceptionHandlerClient(); + //! \brief Communicates with the handler to determine its credentials. + //! + //! If using a multi-client socket, this method should be called before + //! sharing the client socket end, or the handler's response may not be + //! received. + //! + //! \param[out] creds The handler process' credentials, valid if this method + //! returns `true`. + //! \return `true` on success. Otherwise, `false` with a message logged. + bool GetHandlerCredentials(ucred* creds); + //! \brief Request a crash dump from the ExceptionHandlerServer. //! //! This method blocks until the crash dump is complete. //! //! \param[in] info Information about this client. //! \return 0 on success or an error code on failure. - int RequestCrashDump(const ClientInformation& info); + int RequestCrashDump(const ExceptionHandlerProtocol::ClientInformation& info); //! \brief Uses `prctl(PR_SET_PTRACER, ...)` to set the process with //! process ID \a pid as the ptracer for this process. @@ -53,12 +67,17 @@ class ExceptionHandlerClient { void SetCanSetPtracer(bool can_set_ptracer); private: - int SendCrashDumpRequest(const ClientInformation& info); + int SendCrashDumpRequest( + const ExceptionHandlerProtocol::ClientInformation& info, + VMAddress stack_pointer); + int SignalCrashDump(const ExceptionHandlerProtocol::ClientInformation& info, + VMAddress stack_pointer); int WaitForCrashDumpComplete(); int server_sock_; pid_t ptracer_; bool can_set_ptracer_; + bool multiple_clients_; DISALLOW_COPY_AND_ASSIGN(ExceptionHandlerClient); }; diff --git a/util/linux/exception_handler_protocol.cc b/util/linux/exception_handler_protocol.cc index 3feca698..45590c82 100644 --- a/util/linux/exception_handler_protocol.cc +++ b/util/linux/exception_handler_protocol.cc @@ -16,10 +16,18 @@ namespace crashpad { -ClientInformation::ClientInformation() - : exception_information_address(0), sanitization_information_address(0) {} +ExceptionHandlerProtocol::ClientInformation::ClientInformation() + : exception_information_address(0), + sanitization_information_address(0) +#if defined(OS_LINUX) + , crash_loop_before_time(0) +#endif // OS_LINUX +{} -ClientToServerMessage::ClientToServerMessage() - : version(kVersion), type(kCrashDumpRequest), client_info() {} +ExceptionHandlerProtocol::ClientToServerMessage::ClientToServerMessage() + : version(kVersion), + type(kTypeCrashDumpRequest), + requesting_thread_stack_address(0), + client_info() {} } // namespace crashpad diff --git a/util/linux/exception_handler_protocol.h b/util/linux/exception_handler_protocol.h index 4ba1b5d0..7312b9d1 100644 --- a/util/linux/exception_handler_protocol.h +++ b/util/linux/exception_handler_protocol.h @@ -16,81 +16,119 @@ #define CRASHPAD_UTIL_LINUX_EXCEPTION_HANDLER_PROTOCOL_H_ #include +#include #include #include +#include "base/macros.h" +#include "build/build_config.h" #include "util/file/file_io.h" #include "util/misc/address_types.h" namespace crashpad { +class ExceptionHandlerProtocol { + public: #pragma pack(push, 1) -//! \brief The type used for error reporting. -using Errno = int32_t; -static_assert(sizeof(Errno) >= sizeof(errno), "Errno type is too small"); + //! \brief The type used for error reporting. + using Errno = int32_t; + static_assert(sizeof(Errno) >= sizeof(errno), "Errno type is too small"); -//! \brief A boolean status suitable for communication between processes. -enum Bool : char { kBoolFalse, kBoolTrue }; + //! \brief A boolean status suitable for communication between processes. + enum Bool : char { kBoolFalse, kBoolTrue }; -//! \brief Information about a client registered with an ExceptionHandlerServer. -struct ClientInformation { - //! \brief Constructs this object. - ClientInformation(); + //! \brief Information about a client registered with an + //! ExceptionHandlerServer. + struct ClientInformation { + //! \brief Constructs this object. + ClientInformation(); - //! \brief The address in the client's address space of an - //! ExceptionInformation struct. - VMAddress exception_information_address; + //! \brief The address in the client's address space of an + //! ExceptionInformation struct. + VMAddress exception_information_address; - //! \brief The address in the client's address space of a - //! SanitizationInformation struct, or 0 if there is no such struct. - VMAddress sanitization_information_address; -}; + //! \brief The address in the client's address space of a + //! SanitizationInformation struct, or 0 if there is no such struct. + VMAddress sanitization_information_address; -//! \brief The message passed from client to server. -struct ClientToServerMessage { - static constexpr int32_t kVersion = 1; - - //! \brief Constructs this object. - ClientToServerMessage(); - - //! \brief Indicates what message version is being used. - int32_t version; - - enum Type : uint32_t { - //! \brief Used to request a crash dump for the sending client. - kCrashDumpRequest - } type; - - union { - //! \brief Valid for type == kCrashDumpRequest - ClientInformation client_info; +#if defined(OS_LINUX) + //! \brief Indicates that the client is likely in a crash loop if a crash + //! occurs before this timestamp. This value is only used by ChromeOS's + //! `/sbin/crash_reporter`. + uint64_t crash_loop_before_time; +#endif }; -}; -//! \brief The message passed from server to client. -struct ServerToClientMessage { - enum Type : uint32_t { - //! \brief Indicates that the client should fork a PtraceBroker process. - kTypeForkBroker, + //! \brief The signal used to indicate a crash dump is complete. + //! + //! When multiple clients share a single socket connection with the handler, + //! the handler sends this signal to the dump requestor to indicate when the + //! the dump is either done or has failed and the client may continue. + static constexpr int kDumpDoneSignal = SIGCONT; - //! \brief Inidicates that the client should set allow the handler to trace - //! it using PR_SET_PTRACER. - kTypeSetPtracer, + //! \brief The message passed from client to server. + struct ClientToServerMessage { + static constexpr int32_t kVersion = 1; - //! \brief Indicates that the handler has completed a requested crash dump. - kTypeCrashDumpComplete, + //! \brief Constructs this object. + ClientToServerMessage(); - //! \brief Indicicates that the handler was unable to produce a crash dump. - kTypeCrashDumpFailed - } type; + //! \brief Indicates what message version is being used. + int32_t version; - //! \brief The handler's process ID. Valid for kTypeSetPtracer. - pid_t pid; -}; + enum Type : uint32_t { + //! \brief Request that the server respond with its credentials. + kTypeCheckCredentials, + + //! \brief Used to request a crash dump for the sending client. + kTypeCrashDumpRequest + }; + + Type type; + + //! \brief A stack address of the thread sending the message. + VMAddress requesting_thread_stack_address; + + union { + //! \brief Valid for type == kCrashDumpRequest + ClientInformation client_info; + }; + }; + + //! \brief The message passed from server to client. + struct ServerToClientMessage { + enum Type : uint32_t { + //! \brief Used to pass credentials with `SCM_CREDENTIALS`. + kTypeCredentials, + + //! \brief Indicates that the client should fork a PtraceBroker process. + kTypeForkBroker, + + //! \brief Inidicates that the client should set allow the handler to + //! trace it using PR_SET_PTRACER. + kTypeSetPtracer, + + //! \brief Indicates that the handler has completed a requested crash + //! dump. + kTypeCrashDumpComplete, + + //! \brief Indicicates that the handler was unable to produce a crash + //! dump. + kTypeCrashDumpFailed + }; + + Type type; + + //! \brief The handler's process ID. Valid for kTypeSetPtracer. + pid_t pid; + }; #pragma pack(pop) + DISALLOW_IMPLICIT_CONSTRUCTORS(ExceptionHandlerProtocol); +}; + } // namespace crashpad #endif // CRASHPAD_UTIL_LINUX_EXCEPTION_HANDLER_PROTOCOL_H_ diff --git a/util/linux/initial_signal_dispositions.cc b/util/linux/initial_signal_dispositions.cc new file mode 100644 index 00000000..b72b2476 --- /dev/null +++ b/util/linux/initial_signal_dispositions.cc @@ -0,0 +1,78 @@ +// Copyright 2020 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/linux/initial_signal_dispositions.h" + +#include +#include + +#include "base/logging.h" +#include "build/build_config.h" + +#if __ANDROID_API__ <= 23 + +namespace crashpad { +namespace { +bool LoggingSignal(int signum, sighandler_t handler) { + sighandler_t previous = signal(signum, handler); + PLOG_IF(ERROR, previous == SIG_ERR) << "signal " << signum; + return previous != SIG_ERR; +} +} // namespace + +#endif // __ANDROID_API__ <= 23 + +bool InitializeSignalDispositions() { +#if __ANDROID_API__ <= 23 + const int api_level = android_get_device_api_level(); + if (api_level < 0) { + LOG(WARNING) << "bad api level"; + return false; + } + + // Bionic installs signal handlers which request crash dumps from Android's + // debuggerd, but there are errors in how signals which aren't automatically + // re-raised are handled on Marshmallow (API 23). + // + // Before requesting a dump, Bionic acquires a lock to communicate with + // debuggerd and expecting imminent death, never releases it. + // + // While handling the dump request, debuggerd allows the dying process to + // continue before ptrace-detaching it. So, when Bionic manually re-raises a + // signal, it is intercepted by debuggerd and the dying process is allowed to + // live. + // + // Bionic restores SIG_DFL for the signal it's just handled, but if a + // different crash signal is later recieved, Bionic attempts to reacquire the + // lock to communicate with debuggerd and blocks forever. + // + // Disable Bionic's signal handlers for these signals on Marshmallow. + bool success = true; + if (api_level == 23) { + success = LoggingSignal(SIGABRT, SIG_DFL); + success = LoggingSignal(SIGFPE, SIG_DFL) && success; + success = LoggingSignal(SIGPIPE, SIG_DFL) && success; +#if defined(SIGSTKFLT) + success = LoggingSignal(SIGSTKFLT, SIG_DFL) && success; +#endif + success = LoggingSignal(SIGTRAP, SIG_DFL) && success; + } + + return success; +#else + return true; +#endif // __ANDROID_API__ <= 23 +} + +} // namespace crashpad diff --git a/util/linux/initial_signal_dispositions.h b/util/linux/initial_signal_dispositions.h new file mode 100644 index 00000000..b67083c7 --- /dev/null +++ b/util/linux/initial_signal_dispositions.h @@ -0,0 +1,41 @@ +// Copyright 2020 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_LINUX_INITIAL_SIGNAL_DISPOSITIONS_H +#define CRASHPAD_UTIL_LINUX_INITIAL_SIGNAL_DISPOSITIONS_H + +namespace crashpad { + +//! \brief Establishes signal dispositions for a process based on the platform. +//! +//! Default signal dispositions are normally configured by the kernel, but +//! additional signal handlers might be installed by dependent or preloaded +//! libraries, e.g. Bionic normally installs signal handlers which log stack +//! traces to Android's logcat. +//! +//! This function initializes signal dispositions when the default dispositions +//! provided by the platform are broken. This function must be called before any +//! application level signal handlers have been installed and should be called +//! early in the process lifetime to reduce the chance of any broken signal +//! handlers being triggered. +//! +//! When running on Android M (API 23), this function installs `SIG_DFL` for +//! signals: `SIGABRT`, `SIGFPE`, `SIGPIPE`, `SIGSTKFLT`, and `SIGTRAP`. +//! +//! \return `true` on success. Otherwise `false` with a message logged. +bool InitializeSignalDispositions(); + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_LINUX_INITIAL_SIGNAL_DISPOSITIONS_H diff --git a/util/linux/memory_map.cc b/util/linux/memory_map.cc index 345f63ca..0890cd61 100644 --- a/util/linux/memory_map.cc +++ b/util/linux/memory_map.cc @@ -18,6 +18,7 @@ #include #include +#include "base/bit_cast.h" #include "base/files/file_path.h" #include "base/logging.h" #include "build/build_config.h" @@ -30,31 +31,9 @@ namespace crashpad { namespace { -// This function is used in this file specfically for signed or unsigned longs. -// longs are typically either int or int64 sized, but pointers to longs are not -// automatically coerced to pointers to ints when they are the same size. -// Simply adding a StringToNumber for longs doesn't work since sometimes long -// and int64_t are actually the same type, resulting in a redefinition error. -template -bool LocalStringToNumber(const std::string& string, Type* number) { - static_assert(sizeof(Type) == sizeof(int) || sizeof(Type) == sizeof(int64_t), - "Unexpected Type size"); - - if (sizeof(Type) == sizeof(int)) { - return std::numeric_limits::is_signed - ? StringToNumber(string, reinterpret_cast(number)) - : StringToNumber(string, - reinterpret_cast(number)); - } else { - return std::numeric_limits::is_signed - ? StringToNumber(string, reinterpret_cast(number)) - : StringToNumber(string, reinterpret_cast(number)); - } -} - template bool HexStringToNumber(const std::string& string, Type* number) { - return LocalStringToNumber("0x" + string, number); + return StringToNumber("0x" + string, number); } // The result from parsing a line from the maps file. @@ -179,7 +158,7 @@ ParseResult ParseMapsLine(DelimitedFileReader* maps_file_reader, if (maps_file_reader->GetDelim(' ', &field) != DelimitedFileReader::Result::kSuccess || - (field.pop_back(), !LocalStringToNumber(field, &mapping.inode))) { + (field.pop_back(), !StringToNumber(field, &mapping.inode))) { LOG(ERROR) << "format error"; return ParseResult::kError; } @@ -204,6 +183,48 @@ ParseResult ParseMapsLine(DelimitedFileReader* maps_file_reader, return ParseResult::kSuccess; } +class SparseReverseIterator : public MemoryMap::Iterator { + public: + SparseReverseIterator(const std::vector& mappings) + : mappings_(mappings), riter_(mappings_.rbegin()) {} + + SparseReverseIterator() : mappings_(), riter_(mappings_.rend()) {} + + // Iterator: + const MemoryMap::Mapping* Next() override { + return riter_ == mappings_.rend() ? nullptr : *(riter_++); + } + + unsigned int Count() override { return mappings_.rend() - riter_; } + + private: + std::vector mappings_; + std::vector::reverse_iterator riter_; + + DISALLOW_COPY_AND_ASSIGN(SparseReverseIterator); +}; + +class FullReverseIterator : public MemoryMap::Iterator { + public: + FullReverseIterator( + std::vector::const_reverse_iterator rbegin, + std::vector::const_reverse_iterator rend) + : riter_(rbegin), rend_(rend) {} + + // Iterator: + const MemoryMap::Mapping* Next() override { + return riter_ == rend_ ? nullptr : &*riter_++; + } + + unsigned int Count() override { return rend_ - riter_; } + + private: + std::vector::const_reverse_iterator riter_; + std::vector::const_reverse_iterator rend_; + + DISALLOW_COPY_AND_ASSIGN(FullReverseIterator); +}; + } // namespace MemoryMap::Mapping::Mapping() @@ -296,7 +317,7 @@ const MemoryMap::Mapping* MemoryMap::FindMappingWithName( return nullptr; } -std::vector MemoryMap::FindFilePossibleMmapStarts( +std::unique_ptr MemoryMap::FindFilePossibleMmapStarts( const Mapping& mapping) const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); @@ -308,27 +329,74 @@ std::vector MemoryMap::FindFilePossibleMmapStarts( for (const auto& candidate : mappings_) { if (mapping.Equals(candidate)) { possible_starts.push_back(&candidate); - return possible_starts; + return std::make_unique(possible_starts); } } LOG(ERROR) << "mapping not found"; - return std::vector(); + return std::make_unique(); } +#if defined(OS_ANDROID) + // The Android Chromium linker uses ashmem to share RELRO segments between + // processes. The original RELRO segment has been unmapped and replaced with a + // mapping named "/dev/ashmem/RELRO:" where is the base + // library name (e.g. libchrome.so) sans any preceding path that may be + // present in other mappings for the library. + // https://crashpad.chromium.org/bug/253 + static constexpr char kRelro[] = "/dev/ashmem/RELRO:"; + if (mapping.name.compare(0, strlen(kRelro), kRelro, 0, strlen(kRelro)) == 0) { + // The kernel appends "(deleted)" to ashmem mappings because there isn't + // any corresponding file on the filesystem. + static constexpr char kDeleted[] = " (deleted)"; + size_t libname_end = mapping.name.rfind(kDeleted); + DCHECK_NE(libname_end, std::string::npos); + if (libname_end == std::string::npos) { + libname_end = mapping.name.size(); + } + + std::string libname = + mapping.name.substr(strlen(kRelro), libname_end - strlen(kRelro)); + for (const auto& candidate : mappings_) { + if (candidate.name.rfind(libname) != std::string::npos) { + possible_starts.push_back(&candidate); + } + if (mapping.Equals(candidate)) { + return std::make_unique(possible_starts); + } + } + } +#endif // OS_ANDROID + for (const auto& candidate : mappings_) { if (candidate.device == mapping.device && - candidate.inode == mapping.inode && - candidate.offset == 0) { + candidate.inode == mapping.inode +#if !defined(OS_ANDROID) + // Libraries on Android may be mapped from zipfiles (APKs), in which + // case the offset is not 0. + && candidate.offset == 0 +#endif // !defined(OS_ANDROID) + ) { possible_starts.push_back(&candidate); } if (mapping.Equals(candidate)) { - return possible_starts; + return std::make_unique(possible_starts); } } LOG(ERROR) << "mapping not found"; - return std::vector(); + return std::make_unique(); +} + +std::unique_ptr MemoryMap::ReverseIteratorFrom( + const Mapping& target) const { + for (auto riter = mappings_.crbegin(); riter != mappings_.rend(); ++riter) { + if (riter->Equals(target)) { + return std::make_unique(riter, mappings_.rend()); + } + } + return std::make_unique(mappings_.rend(), + mappings_.rend()); } } // namespace crashpad diff --git a/util/linux/memory_map.h b/util/linux/memory_map.h index af194e99..d43b7af4 100644 --- a/util/linux/memory_map.h +++ b/util/linux/memory_map.h @@ -17,6 +17,7 @@ #include +#include #include #include @@ -42,7 +43,7 @@ class MemoryMap { std::string name; CheckedLinuxAddressRange range; - off_t offset; + off64_t offset; dev_t device; ino_t inode; bool readable; @@ -76,14 +77,37 @@ class MemoryMap { //! it was obtained from. const Mapping* FindMappingWithName(const std::string& name) const; - //! \brief Find Mappings that share a Mapping's file, mapped from offset 0. + //! \brief An abstract base class for iterating over ordered sets of mappings + //! in a MemoryMap. + class Iterator { + public: + virtual ~Iterator() = default; + + //! \return the mapping pointed to by the iterator and advance the iterator + //! to the next mapping. If there are no more mappings, this method + //! returns `nullptr` on all subsequent invocations. + virtual const Mapping* Next() = 0; + + //! \return the number of mappings remaining. + virtual unsigned int Count() = 0; + + protected: + Iterator() = default; + }; + + //! \brief Find possible initial mappings of files mapped over several + //! segments. //! //! Executables and libaries are typically loaded into several mappings with //! varying permissions for different segments. Portions of an ELF file may //! be mapped multiple times as part of loading the file, for example, when - //! initializing GNU_RELRO segments. This method searches for mappings at or - //! below \a mapping in memory that are mapped from the same file as \a - //! mapping from offset 0. + //! initializing GNU_RELRO segments. + //! + //! This method searches for mappings at or below \a mapping in memory that + //! are mapped from the same file as \a mapping from offset 0. + //! + //! On Android, ELF modules may be loaded from within a zipfile, so this + //! method may return mappings whose offset is not 0. //! //! This method is intended to help identify the possible base address for //! loaded modules, but it is the caller's responsibility to determine which @@ -94,10 +118,15 @@ class MemoryMap { //! map a file, \a mapping is returned in \a possible_starts. //! //! \param[in] mapping A Mapping whose series to find the start of. - //! \return a vector of the possible mapping starts. - std::vector FindFilePossibleMmapStarts( + //! \return a reverse iterator over the possible mapping starts, starting from + //! the mapping with highest base address. + std::unique_ptr FindFilePossibleMmapStarts( const Mapping& mapping) const; + //! \return A reverse iterator over all mappings in the MemoryMap from \a + //! mapping to the start of the MemoryMap. + std::unique_ptr ReverseIteratorFrom(const Mapping& mapping) const; + private: std::vector mappings_; InitializationStateDcheck initialized_; diff --git a/util/linux/memory_map_test.cc b/util/linux/memory_map_test.cc index 645a0b4d..0ee90800 100644 --- a/util/linux/memory_map_test.cc +++ b/util/linux/memory_map_test.cc @@ -29,7 +29,9 @@ #include "test/linux/fake_ptrace_connection.h" #include "test/multiprocess.h" #include "test/scoped_temp_dir.h" +#include "third_party/lss/lss.h" #include "util/file/file_io.h" +#include "util/file/scoped_remove_file.h" #include "util/linux/direct_ptrace_connection.h" #include "util/misc/clock.h" #include "util/misc/from_pointer_cast.h" @@ -39,6 +41,38 @@ namespace crashpad { namespace test { namespace { +TEST(MemoryMap, SelfLargeFiles) { + // This test is meant to test the handler's ability to understand files + // mapped from large offsets, even if the handler wasn't built with + // _FILE_OFFSET_BITS=64. ScopedTempDir needs to stat files to determine + // whether to recurse into directories, which may will fail without large file + // support. ScopedRemoveFile doesn't have that restriction. + ScopedTempDir dir; + ScopedRemoveFile large_file_path(dir.path().Append("crashpad_test_file")); + ScopedFileHandle handle( + LoggingOpenFileForReadAndWrite(large_file_path.get(), + FileWriteMode::kCreateOrFail, + FilePermissions::kWorldReadable)); + ASSERT_TRUE(handle.is_valid()); + + // sys_fallocate supports large files as long as the kernel supports them, + // regardless of _FILE_OFFSET_BITS. + off64_t off = 1llu + UINT32_MAX; + ASSERT_EQ(sys_fallocate(handle.get(), 0, off, getpagesize()), 0) + << ErrnoMessage("fallocate"); + + ScopedMmap mapping; + void* addr = sys_mmap( + nullptr, getpagesize(), PROT_READ, MAP_SHARED, handle.get(), off); + ASSERT_TRUE(addr); + ASSERT_TRUE(mapping.ResetAddrLen(addr, getpagesize())); + + FakePtraceConnection connection; + ASSERT_TRUE(connection.Initialize(getpid())); + MemoryMap map; + ASSERT_TRUE(map.Initialize(&connection)); +} + TEST(MemoryMap, SelfBasic) { ScopedMmap mmapping; ASSERT_TRUE(mmapping.ResetMmap(nullptr, @@ -67,7 +101,10 @@ TEST(MemoryMap, SelfBasic) { ASSERT_TRUE(mapping); EXPECT_GE(code_address, mapping->range.Base()); EXPECT_LT(code_address, mapping->range.End()); +#if !defined(OS_ANDROID) + // Android Q+ supports execute only memory. EXPECT_TRUE(mapping->readable); +#endif EXPECT_FALSE(mapping->writable); EXPECT_TRUE(mapping->executable); @@ -133,7 +170,10 @@ class MapChildTest : public Multiprocess { ASSERT_TRUE(mapping); EXPECT_GE(code_address, mapping->range.Base()); EXPECT_LT(code_address, mapping->range.End()); +#if !defined(OS_ANDROID) + // Android Q+ supports execute only memory. EXPECT_TRUE(mapping->readable); +#endif EXPECT_TRUE(mapping->executable); EXPECT_FALSE(mapping->writable); @@ -373,19 +413,21 @@ void ExpectFindFilePossibleMmapStarts(LinuxVMAddress mapping_start, ASSERT_NE(mapping1, mapping2); ASSERT_NE(mapping2, mapping3); - std::vector mappings; - - mappings = map.FindFilePossibleMmapStarts(*mapping1); - ASSERT_EQ(mappings.size(), 1u); - EXPECT_EQ(mappings[0], mapping1); + auto mappings = map.FindFilePossibleMmapStarts(*mapping1); + ASSERT_EQ(mappings->Count(), 1u); + EXPECT_EQ(mappings->Next(), mapping1); mappings = map.FindFilePossibleMmapStarts(*mapping2); - ASSERT_EQ(mappings.size(), 1u); - EXPECT_EQ(mappings[0], mapping2); + ASSERT_EQ(mappings->Count(), 1u); + EXPECT_EQ(mappings->Next(), mapping2); mappings = map.FindFilePossibleMmapStarts(*mapping3); - ASSERT_EQ(mappings.size(), 1u); - EXPECT_EQ(mappings[0], mapping1); +#if defined(OS_ANDROID) + EXPECT_EQ(mappings->Count(), 2u); +#else + ASSERT_EQ(mappings->Count(), 1u); + EXPECT_EQ(mappings->Next(), mapping1); +#endif } TEST(MemoryMap, FindFilePossibleMmapStarts) { @@ -428,19 +470,31 @@ TEST(MemoryMap, FindFilePossibleMmapStarts) { ASSERT_NE(mapping1, mapping2); ASSERT_NE(mapping2, mapping3); - std::vector mappings; - - mappings = map.FindFilePossibleMmapStarts(*mapping1); - ASSERT_EQ(mappings.size(), 1u); - EXPECT_EQ(mappings[0], mapping1); +#if defined(OS_ANDROID) + auto mappings = map.FindFilePossibleMmapStarts(*mapping1); + EXPECT_EQ(mappings->Count(), 1u); + EXPECT_EQ(mappings->Next(), mapping1); + EXPECT_EQ(mappings->Next(), nullptr); mappings = map.FindFilePossibleMmapStarts(*mapping2); - ASSERT_EQ(mappings.size(), 1u); - EXPECT_EQ(mappings[0], mapping1); + EXPECT_EQ(mappings->Count(), 2u); mappings = map.FindFilePossibleMmapStarts(*mapping3); - ASSERT_EQ(mappings.size(), 1u); - EXPECT_EQ(mappings[0], mapping1); + EXPECT_EQ(mappings->Count(), 3u); +#else + auto mappings = map.FindFilePossibleMmapStarts(*mapping1); + ASSERT_EQ(mappings->Count(), 1u); + EXPECT_EQ(mappings->Next(), mapping1); + EXPECT_EQ(mappings->Next(), nullptr); + + mappings = map.FindFilePossibleMmapStarts(*mapping2); + ASSERT_EQ(mappings->Count(), 1u); + EXPECT_EQ(mappings->Next(), mapping1); + + mappings = map.FindFilePossibleMmapStarts(*mapping3); + ASSERT_EQ(mappings->Count(), 1u); + EXPECT_EQ(mappings->Next(), mapping1); +#endif #if defined(ARCH_CPU_64_BITS) constexpr bool is_64_bit = true; @@ -449,7 +503,9 @@ TEST(MemoryMap, FindFilePossibleMmapStarts) { #endif MemoryMap::Mapping bad_mapping; bad_mapping.range.SetRange(is_64_bit, 0, 1); - EXPECT_EQ(map.FindFilePossibleMmapStarts(bad_mapping).size(), 0u); + mappings = map.FindFilePossibleMmapStarts(bad_mapping); + EXPECT_EQ(mappings->Count(), 0u); + EXPECT_EQ(mappings->Next(), nullptr); } // Make the second page an anonymous mapping @@ -562,27 +618,47 @@ TEST(MemoryMap, FindFilePossibleMmapStarts_MultipleStarts) { auto mapping = map.FindMapping(file_mapping0.addr_as()); ASSERT_TRUE(mapping); auto possible_starts = map.FindFilePossibleMmapStarts(*mapping); - EXPECT_EQ(possible_starts.size(), 0u); +#if defined(OS_ANDROID) + EXPECT_EQ(possible_starts->Count(), 1u); +#else + EXPECT_EQ(possible_starts->Count(), 0u); +#endif mapping = map.FindMapping(file_mapping1.addr_as()); ASSERT_TRUE(mapping); possible_starts = map.FindFilePossibleMmapStarts(*mapping); - EXPECT_EQ(possible_starts.size(), 1u); +#if defined(OS_ANDROID) + EXPECT_EQ(possible_starts->Count(), 2u); +#else + EXPECT_EQ(possible_starts->Count(), 1u); +#endif mapping = map.FindMapping(file_mapping2.addr_as()); ASSERT_TRUE(mapping); possible_starts = map.FindFilePossibleMmapStarts(*mapping); - EXPECT_EQ(possible_starts.size(), 2u); +#if defined(OS_ANDROID) + EXPECT_EQ(possible_starts->Count(), 3u); +#else + EXPECT_EQ(possible_starts->Count(), 2u); +#endif mapping = map.FindMapping(file_mapping3.addr_as()); ASSERT_TRUE(mapping); possible_starts = map.FindFilePossibleMmapStarts(*mapping); - EXPECT_EQ(possible_starts.size(), 3u); +#if defined(OS_ANDROID) + EXPECT_EQ(possible_starts->Count(), 4u); +#else + EXPECT_EQ(possible_starts->Count(), 3u); +#endif mapping = map.FindMapping(file_mapping4.addr_as()); ASSERT_TRUE(mapping); possible_starts = map.FindFilePossibleMmapStarts(*mapping); - EXPECT_EQ(possible_starts.size(), 4u); +#if defined(OS_ANDROID) + EXPECT_EQ(possible_starts->Count(), 5u); +#else + EXPECT_EQ(possible_starts->Count(), 4u); +#endif } } // namespace diff --git a/util/linux/proc_stat_reader.cc b/util/linux/proc_stat_reader.cc index b1dfe94f..dd663d6c 100644 --- a/util/linux/proc_stat_reader.cc +++ b/util/linux/proc_stat_reader.cc @@ -20,6 +20,7 @@ #include "base/files/file_path.h" #include "base/logging.h" +#include "base/stl_util.h" #include "util/file/file_io.h" #include "util/misc/lexing.h" #include "util/misc/time.h" @@ -43,9 +44,12 @@ ProcStatReader::ProcStatReader() ProcStatReader::~ProcStatReader() {} -bool ProcStatReader::Initialize(pid_t tid) { +bool ProcStatReader::Initialize(PtraceConnection* connection, pid_t tid) { INITIALIZATION_STATE_SET_INITIALIZING(initialized_); - if (!ReadFile(tid)) { + + char path[32]; + snprintf(path, base::size(path), "/proc/%d/stat", tid); + if (!connection->ReadFileContents(base::FilePath(path), &contents_)) { return false; } @@ -81,7 +85,8 @@ bool ProcStatReader::SystemCPUTime(timeval* system_time) const { return ReadTimeAtIndex(14, system_time); } -bool ProcStatReader::StartTime(timeval* start_time) const { +bool ProcStatReader::StartTime(const timeval& boot_time, + timeval* start_time) const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); timeval time_after_boot; @@ -89,33 +94,7 @@ bool ProcStatReader::StartTime(timeval* start_time) const { return false; } - timespec uptime; - if (clock_gettime(CLOCK_BOOTTIME, &uptime) != 0) { - PLOG(ERROR) << "clock_gettime"; - return false; - } - - timespec current_time; - if (clock_gettime(CLOCK_REALTIME, ¤t_time) != 0) { - PLOG(ERROR) << "clock_gettime"; - return false; - } - - timespec boot_time_ts; - SubtractTimespec(current_time, uptime, &boot_time_ts); - timeval boot_time_tv; - TimespecToTimeval(boot_time_ts, &boot_time_tv); - timeradd(&boot_time_tv, &time_after_boot, start_time); - - return true; -} - -bool ProcStatReader::ReadFile(pid_t tid) { - char path[32]; - snprintf(path, arraysize(path), "/proc/%d/stat", tid); - if (!LoggingReadEntireFile(base::FilePath(path), &contents_)) { - return false; - } + timeradd(&boot_time, &time_after_boot, start_time); return true; } diff --git a/util/linux/proc_stat_reader.h b/util/linux/proc_stat_reader.h index 4cad97c5..6eae8fbb 100644 --- a/util/linux/proc_stat_reader.h +++ b/util/linux/proc_stat_reader.h @@ -22,6 +22,7 @@ #include #include "base/macros.h" +#include "util/linux/ptrace_connection.h" #include "util/misc/initialization_state_dcheck.h" namespace crashpad { @@ -36,8 +37,10 @@ class ProcStatReader { //! //! This method must be successfully called before calling any other. //! + //! \param[in] connection A connection to the process to which the target + //! thread belongs. //! \param[in] tid The thread ID to read the stat file for. - bool Initialize(pid_t tid); + bool Initialize(PtraceConnection* connection, pid_t tid); //! \brief Determines the time the thread has spent executing in user mode. //! @@ -57,14 +60,14 @@ class ProcStatReader { //! \brief Determines the target thread’s start time. //! + //! \param[in] boot_time The kernel boot time. //! \param[out] start_time The time that the thread started. //! //! \return `true` on success, with \a start_time set. Otherwise, `false` with //! a message logged. - bool StartTime(timeval* start_time) const; + bool StartTime(const timeval& boot_time, timeval* start_time) const; private: - bool ReadFile(pid_t tid); bool FindColumn(int index, const char** column) const; bool ReadTimeAtIndex(int index, timeval* time_val) const; diff --git a/util/linux/proc_stat_reader_test.cc b/util/linux/proc_stat_reader_test.cc index 8189c2ea..01b9afd7 100644 --- a/util/linux/proc_stat_reader_test.cc +++ b/util/linux/proc_stat_reader_test.cc @@ -20,6 +20,8 @@ #include "base/logging.h" #include "gtest/gtest.h" +#include "test/linux/fake_ptrace_connection.h" +#include "util/misc/time.h" #include "util/thread/thread.h" namespace crashpad { @@ -27,11 +29,19 @@ namespace test { namespace { TEST(ProcStatReader, Basic) { + FakePtraceConnection connection; + ASSERT_TRUE(connection.Initialize(getpid())); + ProcStatReader stat; - ASSERT_TRUE(stat.Initialize(getpid())); + ASSERT_TRUE(stat.Initialize(&connection, getpid())); + + timespec boot_time_ts; + ASSERT_TRUE(GetBootTime(&boot_time_ts)); + timeval boot_time; + TimespecToTimeval(boot_time_ts, &boot_time); timeval start_time; - ASSERT_TRUE(stat.StartTime(&start_time)); + ASSERT_TRUE(stat.StartTime(boot_time, &start_time)); time_t now; time(&now); @@ -52,27 +62,38 @@ pid_t gettid() { return syscall(SYS_gettid); } -void GetStartTime(timeval* start_time) { +void GetStartTime(const timeval& boot_time, timeval* start_time) { + FakePtraceConnection connection; + ASSERT_TRUE(connection.Initialize(getpid())); + ProcStatReader stat; - ASSERT_TRUE(stat.Initialize(gettid())); - ASSERT_TRUE(stat.StartTime(start_time)); + ASSERT_TRUE(stat.Initialize(&connection, gettid())); + ASSERT_TRUE(stat.StartTime(boot_time, start_time)); } class StatTimeThread : public Thread { public: - StatTimeThread(timeval* start_time) : start_time_(start_time) {} + StatTimeThread(const timeval& boot_time, timeval* start_time) + : boot_time_(boot_time), start_time_(start_time) {} private: - void ThreadMain() override { GetStartTime(start_time_); } + void ThreadMain() override { GetStartTime(boot_time_, start_time_); } + + const timeval& boot_time_; timeval* start_time_; }; TEST(ProcStatReader, Threads) { + timespec boot_time_ts; + ASSERT_TRUE(GetBootTime(&boot_time_ts)); + timeval boot_time; + TimespecToTimeval(boot_time_ts, &boot_time); + timeval main_time; - ASSERT_NO_FATAL_FAILURE(GetStartTime(&main_time)); + ASSERT_NO_FATAL_FAILURE(GetStartTime(boot_time, &main_time)); timeval thread_time; - StatTimeThread thread(&thread_time); + StatTimeThread thread(boot_time, &thread_time); thread.Start(); ASSERT_NO_FATAL_FAILURE(thread.Join()); @@ -82,7 +103,7 @@ TEST(ProcStatReader, Threads) { time_t thread_sec, suseconds_t thread_usec) { return (thread_sec > main_sec) || - (thread_sec == main_sec && thread_usec > main_usec); + (thread_sec == main_sec && thread_usec >= main_usec); }, main_time.tv_sec, main_time.tv_usec, diff --git a/util/linux/proc_task_reader.cc b/util/linux/proc_task_reader.cc new file mode 100644 index 00000000..360f83a0 --- /dev/null +++ b/util/linux/proc_task_reader.cc @@ -0,0 +1,59 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/linux/proc_task_reader.h" + +#include + +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/stl_util.h" +#include "base/strings/string_number_conversions.h" +#include "util/file/directory_reader.h" +#include "util/misc/as_underlying_type.h" + +namespace crashpad { + +bool ReadThreadIDs(pid_t pid, std::vector* tids) { + DCHECK(tids->empty()); + + char path[32]; + snprintf(path, base::size(path), "/proc/%d/task", pid); + DirectoryReader reader; + if (!reader.Open(base::FilePath(path))) { + return false; + } + + std::vector local_tids; + base::FilePath tid_str; + DirectoryReader::Result result; + while ((result = reader.NextFile(&tid_str)) == + DirectoryReader::Result::kSuccess) { + pid_t tid; + if (!base::StringToInt(tid_str.value(), &tid)) { + LOG(ERROR) << "format error"; + continue; + } + + local_tids.push_back(tid); + } + DCHECK_EQ(AsUnderlyingType(result), + AsUnderlyingType(DirectoryReader::Result::kNoMoreFiles)); + DCHECK(!local_tids.empty()); + + tids->swap(local_tids); + return true; +} + +} // namespace crashpad diff --git a/util/linux/proc_task_reader.h b/util/linux/proc_task_reader.h new file mode 100644 index 00000000..9b95f127 --- /dev/null +++ b/util/linux/proc_task_reader.h @@ -0,0 +1,35 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_LINUX_PROC_TASK_READER_H_ +#define CRASHPAD_UTIL_LINUX_PROC_TASK_READER_H_ + +#include + +#include + +namespace crashpad { + +//! \brief Enumerates the thread IDs of a process by reading +//! /proc/pid/task. +//! +//! \param[in] pid The process ID for which to read thread IDs. +//! \param[out] tids The read thread IDs. +//! \return `true` if the task directory was successfully read. Format errors +//! are logged, but won't cause this function to return `false`. +bool ReadThreadIDs(pid_t pid, std::vector* tids); + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_LINUX_PROC_TASK_READER_H_ diff --git a/util/linux/proc_task_reader_test.cc b/util/linux/proc_task_reader_test.cc new file mode 100644 index 00000000..911f6d3d --- /dev/null +++ b/util/linux/proc_task_reader_test.cc @@ -0,0 +1,162 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/linux/proc_task_reader.h" + +#include "base/logging.h" +#include "base/macros.h" +#include "base/strings/stringprintf.h" +#include "gtest/gtest.h" +#include "test/multiprocess_exec.h" +#include "third_party/lss/lss.h" +#include "util/synchronization/semaphore.h" +#include "util/thread/thread.h" + +namespace crashpad { +namespace test { +namespace { + +bool FindThreadID(pid_t tid, const std::vector& threads) { + for (const auto& thread : threads) { + if (thread == tid) { + return true; + } + } + return false; +} + +class ScopedBlockingThread : public Thread { + public: + ScopedBlockingThread() : tid_sem_(0), join_sem_(0), tid_(-1) {} + + ~ScopedBlockingThread() { + join_sem_.Signal(); + Join(); + } + + pid_t ThreadID() { + tid_sem_.Wait(); + return tid_; + } + + private: + void ThreadMain() override { + tid_ = sys_gettid(); + tid_sem_.Signal(); + join_sem_.Wait(); + } + + Semaphore tid_sem_; + Semaphore join_sem_; + pid_t tid_; +}; + +TEST(ProcTaskReader, Self) { + std::vector tids; + ASSERT_TRUE(ReadThreadIDs(getpid(), &tids)); + EXPECT_TRUE(FindThreadID(getpid(), tids)); + EXPECT_TRUE(FindThreadID(sys_gettid(), tids)); + + ScopedBlockingThread thread1; + thread1.Start(); + + ScopedBlockingThread thread2; + thread2.Start(); + + pid_t thread1_tid = thread1.ThreadID(); + pid_t thread2_tid = thread2.ThreadID(); + + tids.clear(); + ASSERT_TRUE(ReadThreadIDs(getpid(), &tids)); + EXPECT_TRUE(FindThreadID(getpid(), tids)); + EXPECT_TRUE(FindThreadID(thread1_tid, tids)); + EXPECT_TRUE(FindThreadID(thread2_tid, tids)); +} + +TEST(ProcTaskReader, BadPID) { + std::vector tids; + EXPECT_FALSE(ReadThreadIDs(-1, &tids)); + + tids.clear(); + EXPECT_FALSE(ReadThreadIDs(0, &tids)); +} + +CRASHPAD_CHILD_TEST_MAIN(ProcTaskTestChild) { + FileHandle in = StdioFileHandle(StdioStream::kStandardInput); + FileHandle out = StdioFileHandle(StdioStream::kStandardOutput); + + pid_t tid = getpid(); + CheckedWriteFile(out, &tid, sizeof(tid)); + + tid = sys_gettid(); + CheckedWriteFile(out, &tid, sizeof(tid)); + + ScopedBlockingThread thread1; + thread1.Start(); + + ScopedBlockingThread thread2; + thread2.Start(); + + tid = thread1.ThreadID(); + CheckedWriteFile(out, &tid, sizeof(tid)); + + tid = thread2.ThreadID(); + CheckedWriteFile(out, &tid, sizeof(tid)); + + CheckedReadFileAtEOF(in); + return 0; +} + +class ProcTaskTest : public MultiprocessExec { + public: + ProcTaskTest() : MultiprocessExec() { + SetChildTestMainFunction("ProcTaskTestChild"); + } + + private: + bool ReadIDFromChild(std::vector* threads) { + pid_t tid; + if (!LoggingReadFileExactly(ReadPipeHandle(), &tid, sizeof(tid))) { + return false; + } + threads->push_back(tid); + return true; + } + + void MultiprocessParent() override { + std::vector ids_to_find; + for (size_t id_count = 0; id_count < 4; ++id_count) { + ASSERT_TRUE(ReadIDFromChild(&ids_to_find)); + } + + std::vector threads; + ASSERT_TRUE(ReadThreadIDs(ChildPID(), &threads)); + for (size_t index = 0; index < ids_to_find.size(); ++index) { + SCOPED_TRACE( + base::StringPrintf("index %zd, tid %d", index, ids_to_find[index])); + EXPECT_TRUE(FindThreadID(ids_to_find[index], threads)); + } + } + + DISALLOW_COPY_AND_ASSIGN(ProcTaskTest); +}; + +TEST(ProcTaskReader, ReadChild) { + ProcTaskTest test; + test.Run(); +} + +} // namespace +} // namespace test +} // namespace crashpad diff --git a/util/linux/ptrace_broker.cc b/util/linux/ptrace_broker.cc index e9d30686..155a1e0c 100644 --- a/util/linux/ptrace_broker.cc +++ b/util/linux/ptrace_broker.cc @@ -17,12 +17,14 @@ #include #include #include +#include #include #include #include "base/logging.h" #include "base/posix/eintr_wrapper.h" +#include "util/misc/memory_sanitizer.h" namespace crashpad { @@ -138,9 +140,10 @@ int PtraceBroker::RunImpl() { attach_on_stack = true; } - Bool status = kBoolFalse; + ExceptionHandlerProtocol::Bool status = + ExceptionHandlerProtocol::kBoolFalse; if (attach->ResetAttach(request.tid)) { - status = kBoolTrue; + status = ExceptionHandlerProtocol::kBoolTrue; if (!attach_on_stack) { ++attach_count_; } @@ -150,21 +153,23 @@ int PtraceBroker::RunImpl() { return errno; } - if (status == kBoolFalse) { - Errno error = errno; + if (status == ExceptionHandlerProtocol::kBoolFalse) { + ExceptionHandlerProtocol::Errno error = errno; if (!WriteFile(sock_, &error, sizeof(error))) { return errno; } } - if (attach_on_stack && status == kBoolTrue) { + if (attach_on_stack && status == ExceptionHandlerProtocol::kBoolTrue) { return RunImpl(); } continue; } case Request::kTypeIs64Bit: { - Bool is_64_bit = ptracer_.Is64Bit() ? kBoolTrue : kBoolFalse; + ExceptionHandlerProtocol::Bool is_64_bit = + ptracer_.Is64Bit() ? ExceptionHandlerProtocol::kBoolTrue + : ExceptionHandlerProtocol::kBoolFalse; if (!WriteFile(sock_, &is_64_bit, sizeof(is_64_bit))) { return errno; } @@ -174,15 +179,15 @@ int PtraceBroker::RunImpl() { case Request::kTypeGetThreadInfo: { GetThreadInfoResponse response; response.success = ptracer_.GetThreadInfo(request.tid, &response.info) - ? kBoolTrue - : kBoolFalse; + ? ExceptionHandlerProtocol::kBoolTrue + : ExceptionHandlerProtocol::kBoolFalse; if (!WriteFile(sock_, &response, sizeof(response))) { return errno; } - if (response.success == kBoolFalse) { - Errno error = errno; + if (response.success == ExceptionHandlerProtocol::kBoolFalse) { + ExceptionHandlerProtocol::Errno error = errno; if (!WriteFile(sock_, &error, sizeof(error))) { return errno; } @@ -192,7 +197,9 @@ int PtraceBroker::RunImpl() { case Request::kTypeReadFile: { ScopedFileHandle handle; - int result = ReceiveAndOpenFilePath(request.path.path_length, &handle); + int result = ReceiveAndOpenFilePath(request.path.path_length, + /* is_directory= */ false, + &handle); if (result != 0) { return result; } @@ -217,6 +224,26 @@ int PtraceBroker::RunImpl() { continue; } + case Request::kTypeListDirectory: { + ScopedFileHandle handle; + int result = ReceiveAndOpenFilePath(request.path.path_length, + /* is_directory= */ true, + &handle); + if (result != 0) { + return result; + } + + if (!handle.is_valid()) { + continue; + } + + result = SendDirectory(handle.get()); + if (result != 0) { + return result; + } + continue; + } + case Request::kTypeExit: return 0; } @@ -226,7 +253,7 @@ int PtraceBroker::RunImpl() { } } -int PtraceBroker::SendError(Errno err) { +int PtraceBroker::SendError(ExceptionHandlerProtocol::Errno err) { return WriteFile(sock_, &err, sizeof(err)) ? 0 : errno; } @@ -329,7 +356,36 @@ int PtraceBroker::SendMemory(pid_t pid, VMAddress address, VMSize size) { return 0; } +#if defined(MEMORY_SANITIZER) +// MSan doesn't intercept syscall() and doesn't see that buffer is initialized. +__attribute__((no_sanitize("memory"))) +#endif // defined(MEMORY_SANITIZER) +int PtraceBroker::SendDirectory(FileHandle handle) { + char buffer[4096]; + int rv; + do { + rv = syscall(SYS_getdents64, handle, buffer, sizeof(buffer)); + + if (rv < 0) { + return SendReadError(static_cast(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; +} + int PtraceBroker::ReceiveAndOpenFilePath(VMSize path_length, + bool is_directory, ScopedFileHandle* handle) { char path[std::max(4096, PATH_MAX)]; @@ -346,8 +402,11 @@ int PtraceBroker::ReceiveAndOpenFilePath(VMSize path_length, return SendOpenResult(kOpenResultAccessDenied); } - ScopedFileHandle local_handle( - HANDLE_EINTR(open(path, O_RDONLY | O_CLOEXEC | O_NOCTTY))); + int flags = O_RDONLY | O_CLOEXEC | O_NOCTTY; + if (is_directory) { + flags |= O_DIRECTORY; + } + ScopedFileHandle local_handle(HANDLE_EINTR(open(path, flags))); if (!local_handle.is_valid()) { return SendOpenResult(static_cast(errno)); } diff --git a/util/linux/ptrace_broker.h b/util/linux/ptrace_broker.h index 2e5decdf..6a7bfb72 100644 --- a/util/linux/ptrace_broker.h +++ b/util/linux/ptrace_broker.h @@ -75,6 +75,16 @@ class PtraceBroker { //! errors, followed by an Errno. On success, the bytes read follow. kTypeReadFile, + //! \brief Reads the contents of a directory. The data is returned in a + //! series of messages. The first message is an OpenResult, indicating + //! the validity of the received file path. If the OpenResult is + //! kOpenResultSuccess, the subsequent messages return the contents of + //! the directory as a dirent stream, as read by `getdents64()`. Each + //! subsequent message begins with an int32_t indicating the number of + //! bytes read, 0 for end-of-file, or -1 for errors, followed by an + //! Errno. On success, the bytes read follow. + kTypeListDirectory, + //! \brief Causes the broker to return from Run(), detaching all attached //! threads. Does not respond. kTypeExit @@ -136,7 +146,7 @@ class PtraceBroker { ThreadInfo info; //! \brief Specifies the success or failure of this call. - Bool success; + ExceptionHandlerProtocol::Bool success; }; #pragma pack(pop) @@ -186,13 +196,16 @@ class PtraceBroker { bool AllocateAttachments(); void ReleaseAttachments(); int RunImpl(); - int SendError(Errno err); + int SendError(ExceptionHandlerProtocol::Errno err); int SendReadError(ReadError err); int SendOpenResult(OpenResult result); int SendFileContents(FileHandle handle); + int SendDirectory(FileHandle handle); void TryOpeningMemFile(); int SendMemory(pid_t pid, VMAddress address, VMSize size); - int ReceiveAndOpenFilePath(VMSize path_length, ScopedFileHandle* handle); + int ReceiveAndOpenFilePath(VMSize path_length, + bool is_directory, + ScopedFileHandle* handle); char file_root_buffer_[32]; Ptracer ptracer_; diff --git a/util/linux/ptrace_broker_test.cc b/util/linux/ptrace_broker_test.cc index 9ed1973b..341f2c80 100644 --- a/util/linux/ptrace_broker_test.cc +++ b/util/linux/ptrace_broker_test.cc @@ -155,6 +155,17 @@ class SameBitnessTest : public Multiprocess { client_sock.get(), ChildPID(), /* try_direct_memory= */ false)); EXPECT_EQ(client.GetProcessID(), ChildPID()); + + std::vector threads; + ASSERT_TRUE(client.Threads(&threads)); + EXPECT_EQ(threads.size(), 2u); + if (threads[0] == ChildPID()) { + EXPECT_EQ(threads[1], child2_tid); + } else { + EXPECT_EQ(threads[0], child2_tid); + EXPECT_EQ(threads[1], ChildPID()); + } + EXPECT_TRUE(client.Attach(child2_tid)); EXPECT_EQ(client.Is64Bit(), am_64_bit); diff --git a/util/linux/ptrace_client.cc b/util/linux/ptrace_client.cc index b5ccebaf..f097ad98 100644 --- a/util/linux/ptrace_client.cc +++ b/util/linux/ptrace_client.cc @@ -20,6 +20,8 @@ #include #include "base/logging.h" +#include "base/stl_util.h" +#include "base/strings/string_number_conversions.h" #include "util/file/file_io.h" #include "util/linux/ptrace_broker.h" #include "util/process/process_memory_linux.h" @@ -29,7 +31,7 @@ namespace crashpad { namespace { bool ReceiveAndLogError(int sock, const std::string& operation) { - Errno error; + ExceptionHandlerProtocol::Errno error; if (!LoggingReadFileExactly(sock, &error, sizeof(error))) { return false; } @@ -60,19 +62,19 @@ bool ReceiveAndLogReadError(int sock, const std::string& operation) { } bool AttachImpl(int sock, pid_t tid) { - PtraceBroker::Request request; + PtraceBroker::Request request = {}; request.type = PtraceBroker::Request::kTypeAttach; request.tid = tid; if (!LoggingWriteFile(sock, &request, sizeof(request))) { return false; } - Bool success; + ExceptionHandlerProtocol::Bool success; if (!LoggingReadFileExactly(sock, &success, sizeof(success))) { return false; } - if (success != kBoolTrue) { + if (success != ExceptionHandlerProtocol::kBoolTrue) { ReceiveAndLogError(sock, "PtraceBroker Attach"); return false; } @@ -80,6 +82,48 @@ bool AttachImpl(int sock, pid_t tid) { return true; } +struct Dirent64 { + ino64_t d_ino; + off64_t d_off; + unsigned short d_reclen; + unsigned char d_type; + char d_name[]; +}; + +void ReadDentsAsThreadIDs(char* buffer, + size_t size, + std::vector* threads) { + while (size > offsetof(Dirent64, d_name)) { + auto dirent = reinterpret_cast(buffer); + if (size < dirent->d_reclen) { + LOG(ERROR) << "short dirent"; + break; + } + buffer += dirent->d_reclen; + size -= dirent->d_reclen; + + const size_t max_name_length = + dirent->d_reclen - offsetof(Dirent64, d_name); + size_t name_len = strnlen(dirent->d_name, max_name_length); + if (name_len >= max_name_length) { + LOG(ERROR) << "format error"; + break; + } + + if (strcmp(dirent->d_name, ".") == 0 || strcmp(dirent->d_name, "..") == 0) { + continue; + } + + pid_t tid; + if (!base::StringToInt(dirent->d_name, &tid)) { + LOG(ERROR) << "format error"; + continue; + } + threads->push_back(tid); + } + DCHECK_EQ(size, 0u); +} + } // namespace PtraceClient::PtraceClient() @@ -92,7 +136,7 @@ PtraceClient::PtraceClient() PtraceClient::~PtraceClient() { if (sock_ != kInvalidFileHandle) { - PtraceBroker::Request request; + PtraceBroker::Request request = {}; request.type = PtraceBroker::Request::kTypeExit; LoggingWriteFile(sock_, &request, sizeof(request)); } @@ -107,7 +151,7 @@ bool PtraceClient::Initialize(int sock, pid_t pid, bool try_direct_memory) { return false; } - PtraceBroker::Request request; + PtraceBroker::Request request = {}; request.type = PtraceBroker::Request::kTypeIs64Bit; request.tid = pid_; @@ -115,11 +159,11 @@ bool PtraceClient::Initialize(int sock, pid_t pid, bool try_direct_memory) { return false; } - Bool is_64_bit; + ExceptionHandlerProtocol::Bool is_64_bit; if (!LoggingReadFileExactly(sock_, &is_64_bit, sizeof(is_64_bit))) { return false; } - is_64_bit_ = is_64_bit == kBoolTrue; + is_64_bit_ = is_64_bit == ExceptionHandlerProtocol::kBoolTrue; if (try_direct_memory) { auto direct_mem = std::make_unique(); @@ -153,7 +197,7 @@ bool PtraceClient::Is64Bit() { bool PtraceClient::GetThreadInfo(pid_t tid, ThreadInfo* info) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - PtraceBroker::Request request; + PtraceBroker::Request request = {}; request.type = PtraceBroker::Request::kTypeGetThreadInfo; request.tid = tid; if (!LoggingWriteFile(sock_, &request, sizeof(request))) { @@ -165,7 +209,7 @@ bool PtraceClient::GetThreadInfo(pid_t tid, ThreadInfo* info) { return false; } - if (response.success == kBoolTrue) { + if (response.success == ExceptionHandlerProtocol::kBoolTrue) { *info = response.info; return true; } @@ -178,7 +222,7 @@ bool PtraceClient::ReadFileContents(const base::FilePath& path, std::string* contents) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - PtraceBroker::Request request; + PtraceBroker::Request request = {}; request.type = PtraceBroker::Request::kTypeReadFile; request.path.path_length = path.value().size(); @@ -218,6 +262,51 @@ ProcessMemory* PtraceClient::Memory() { return memory_.get(); } +bool PtraceClient::Threads(std::vector* threads) { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + DCHECK(threads->empty()); + + // If the broker is unable to read thread IDs, fall-back to just the main + // thread's ID. + threads->push_back(pid_); + + char path[32]; + snprintf(path, base::size(path), "/proc/%d/task", pid_); + + PtraceBroker::Request request = {}; + request.type = PtraceBroker::Request::kTypeListDirectory; + request.path.path_length = strlen(path); + + if (!LoggingWriteFile(sock_, &request, sizeof(request)) || + !SendFilePath(path, request.path.path_length)) { + return false; + } + + std::vector local_threads; + int32_t read_result; + do { + if (!LoggingReadFileExactly(sock_, &read_result, sizeof(read_result))) { + return false; + } + + if (read_result < 0) { + return ReceiveAndLogReadError(sock_, "Threads"); + } + + if (read_result > 0) { + auto buffer = std::make_unique(read_result); + if (!LoggingReadFileExactly(sock_, buffer.get(), read_result)) { + return false; + } + + ReadDentsAsThreadIDs(buffer.get(), read_result, &local_threads); + } + } while (read_result > 0); + + threads->swap(local_threads); + return true; +} + PtraceClient::BrokeredMemory::BrokeredMemory(PtraceClient* client) : ProcessMemory(), client_(client) {} @@ -235,7 +324,7 @@ ssize_t PtraceClient::ReadUpTo(VMAddress address, INITIALIZATION_STATE_DCHECK_VALID(initialized_); char* buffer_c = reinterpret_cast(buffer); - PtraceBroker::Request request; + PtraceBroker::Request request = {}; request.type = PtraceBroker::Request::kTypeReadMemory; request.tid = pid_; request.iov.base = address; diff --git a/util/linux/ptrace_client.h b/util/linux/ptrace_client.h index a7abc0cf..b4f27ae7 100644 --- a/util/linux/ptrace_client.h +++ b/util/linux/ptrace_client.h @@ -61,6 +61,7 @@ class PtraceClient : public PtraceConnection { bool ReadFileContents(const base::FilePath& path, std::string* contents) override; ProcessMemory* Memory() override; + bool Threads(std::vector* threads) override; private: class BrokeredMemory : public ProcessMemory { diff --git a/util/linux/ptrace_connection.h b/util/linux/ptrace_connection.h index 86dc2dd9..21117475 100644 --- a/util/linux/ptrace_connection.h +++ b/util/linux/ptrace_connection.h @@ -18,6 +18,7 @@ #include #include +#include #include "base/files/file_path.h" #include "util/linux/thread_info.h" @@ -64,6 +65,14 @@ class PtraceConnection { //! The caller does not take ownership of the reader. The reader is valid for //! the lifetime of the PtraceConnection that created it. virtual ProcessMemory* Memory() = 0; + + //! \brief Determines the thread IDs of the threads in the connected process. + //! + //! \param[out] threads The list of thread IDs. + //! \return `true` on success, `false` on failure with a message logged. If + //! this method returns `false`, \a threads may contain a partial list of + //! thread IDs. + virtual bool Threads(std::vector* threads) = 0; }; } // namespace crashpad diff --git a/util/linux/ptracer.cc b/util/linux/ptracer.cc index c6c92299..557e0d36 100644 --- a/util/linux/ptracer.cc +++ b/util/linux/ptracer.cc @@ -44,7 +44,8 @@ bool GetRegisterSet(pid_t tid, int set, Destination* dest, bool can_log) { return false; } if (iov.iov_len != sizeof(*dest)) { - LOG_IF(ERROR, can_log) << "Unexpected registers size"; + LOG_IF(ERROR, can_log) << "Unexpected registers size " << iov.iov_len + << " != " << sizeof(*dest); return false; } return true; @@ -176,7 +177,8 @@ bool GetFloatingPointRegisters32(pid_t tid, } } else { if (iov.iov_len != sizeof(context->f32.fpregs)) { - LOG_IF(ERROR, can_log) << "Unexpected registers size"; + LOG_IF(ERROR, can_log) << "Unexpected registers size " << iov.iov_len + << " != " << sizeof(context->f32.fpregs); return false; } context->f32.have_fpregs = true; @@ -197,7 +199,8 @@ bool GetFloatingPointRegisters32(pid_t tid, } } else { if (iov.iov_len != kArmVfpSize && iov.iov_len != sizeof(context->f32.vfp)) { - LOG_IF(ERROR, can_log) << "Unexpected registers size"; + LOG_IF(ERROR, can_log) << "Unexpected registers size " << iov.iov_len + << " != " << sizeof(context->f32.vfp); return false; } context->f32.have_vfp = true; @@ -223,7 +226,8 @@ bool GetFloatingPointRegisters64(pid_t tid, return false; } if (iov.iov_len != sizeof(context->f64)) { - LOG_IF(ERROR, can_log) << "Unexpected registers size"; + LOG_IF(ERROR, can_log) << "Unexpected registers size " << iov.iov_len + << " != " << sizeof(context->f64); return false; } return true; @@ -425,9 +429,10 @@ size_t GetGeneralPurposeRegistersAndLength(pid_t tid, bool GetGeneralPurposeRegisters32(pid_t tid, ThreadContext* context, bool can_log) { - if (GetGeneralPurposeRegistersAndLength(tid, context, can_log) != - sizeof(context->t32)) { - LOG_IF(ERROR, can_log) << "Unexpected registers size"; + size_t length = GetGeneralPurposeRegistersAndLength(tid, context, can_log); + if (length != sizeof(context->t32)) { + LOG_IF(ERROR, can_log) << "Unexpected registers size " << length + << " != " << sizeof(context->t32); return false; } return true; @@ -436,9 +441,10 @@ bool GetGeneralPurposeRegisters32(pid_t tid, bool GetGeneralPurposeRegisters64(pid_t tid, ThreadContext* context, bool can_log) { - if (GetGeneralPurposeRegistersAndLength(tid, context, can_log) != - sizeof(context->t64)) { - LOG_IF(ERROR, can_log) << "Unexpected registers size"; + size_t length = GetGeneralPurposeRegistersAndLength(tid, context, can_log); + if (length != sizeof(context->t64)) { + LOG_IF(ERROR, can_log) << "Unexpected registers size " << length + << " != " << sizeof(context->t64); return false; } return true; @@ -467,7 +473,9 @@ bool Ptracer::Initialize(pid_t pid) { } else if (length == sizeof(context.t32)) { is_64_bit_ = false; } else { - LOG_IF(ERROR, can_log_) << "Unexpected registers size"; + LOG_IF(ERROR, can_log_) + << "Unexpected registers size " << length + << " != " << sizeof(context.t64) << ", " << sizeof(context.t32); return false; } diff --git a/util/linux/scoped_pr_set_dumpable.cc b/util/linux/scoped_pr_set_dumpable.cc new file mode 100644 index 00000000..cbec009b --- /dev/null +++ b/util/linux/scoped_pr_set_dumpable.cc @@ -0,0 +1,41 @@ +// Copyright 2018 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/linux/scoped_pr_set_dumpable.h" + +#include + +#include "base/logging.h" + +namespace crashpad { + +ScopedPrSetDumpable::ScopedPrSetDumpable(bool may_log) : may_log_(may_log) { + int result = prctl(PR_GET_DUMPABLE, 0, 0, 0, 0); + PLOG_IF(ERROR, result < 0 && may_log_) << "prctl"; + was_dumpable_ = result > 0; + + if (!was_dumpable_) { + result = prctl(PR_SET_DUMPABLE, 1, 0, 0, 0); + PLOG_IF(ERROR, result != 0 && may_log_) << "prctl"; + } +} + +ScopedPrSetDumpable::~ScopedPrSetDumpable() { + if (!was_dumpable_) { + int result = prctl(PR_SET_DUMPABLE, 0, 0, 0, 0); + PLOG_IF(ERROR, result != 0 && may_log_) << "prctl"; + } +} + +} // namespace crashpad diff --git a/util/linux/scoped_pr_set_dumpable.h b/util/linux/scoped_pr_set_dumpable.h new file mode 100644 index 00000000..16819309 --- /dev/null +++ b/util/linux/scoped_pr_set_dumpable.h @@ -0,0 +1,44 @@ +// Copyright 2018 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_LINUX_SCOPED_PR_SET_DUMPABLE_H_ +#define CRASHPAD_UTIL_LINUX_SCOPED_PR_SET_DUMPABLE_H_ + +#include "base/macros.h" + +namespace crashpad { + +class ScopedPrSetDumpable { + public: + //! \brief Uses `PR_SET_DUMPABLE` to make the current process dumpable. + //! + //! Restores the dumpable flag to its original value on destruction. If the + //! original value couldn't be determined, the destructor attempts to restore + //! the flag to 0 (non-dumpable). + //! + //! \param[in] may_log `true` if this object may log error messages. + explicit ScopedPrSetDumpable(bool may_log); + + ~ScopedPrSetDumpable(); + + private: + bool was_dumpable_; + bool may_log_; + + DISALLOW_COPY_AND_ASSIGN(ScopedPrSetDumpable); +}; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_LINUX_SCOPED_PR_SET_DUMPABLE_H_ diff --git a/util/linux/socket.cc b/util/linux/socket.cc new file mode 100644 index 00000000..f56eacf6 --- /dev/null +++ b/util/linux/socket.cc @@ -0,0 +1,192 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/linux/socket.h" + +#include + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "third_party/lss/lss.h" + +namespace crashpad { + +// static +bool UnixCredentialSocket::CreateCredentialSocketpair(ScopedFileHandle* sock1, + ScopedFileHandle* sock2) { + int socks[2]; + if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, socks) != 0) { + PLOG(ERROR) << "socketpair"; + return false; + } + ScopedFileHandle local_sock1(socks[0]); + ScopedFileHandle local_sock2(socks[1]); + + int optval = 1; + socklen_t optlen = sizeof(optval); + if (setsockopt(local_sock1.get(), SOL_SOCKET, SO_PASSCRED, &optval, optlen) != + 0 || + setsockopt(local_sock2.get(), SOL_SOCKET, SO_PASSCRED, &optval, optlen) != + 0) { + PLOG(ERROR) << "setsockopt"; + return false; + } + + sock1->swap(local_sock1); + sock2->swap(local_sock2); + return true; +} + +constexpr size_t UnixCredentialSocket::kMaxSendRecvMsgFDs = 4; + +// static +int UnixCredentialSocket::SendMsg(int fd, + const void* buf, + size_t buf_size, + const int* fds, + size_t fd_count) { + // This function is intended to be used after a crash. fds is an integer + // array instead of a vector to avoid forcing callers to provide a vector, + // which they would have to create prior to the crash. + if (fds && fd_count > kMaxSendRecvMsgFDs) { + DLOG(ERROR) << "too many fds " << fd_count; + return EINVAL; + } + + iovec iov; + iov.iov_base = const_cast(buf); + iov.iov_len = buf_size; + + msghdr msg = {}; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + char cmsg_buf[CMSG_SPACE(sizeof(int) * kMaxSendRecvMsgFDs)]; + if (fds) { + msg.msg_control = cmsg_buf; + msg.msg_controllen = CMSG_SPACE(sizeof(int) * fd_count); + + cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); + DCHECK(cmsg); + + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int) * fd_count); + memcpy(CMSG_DATA(cmsg), fds, sizeof(int) * fd_count); + } + + // TODO(jperaza): Use sys_sendmsg when lss has macros for maniuplating control + // messages. https://crbug.com/crashpad/265 + if (HANDLE_EINTR(sendmsg(fd, &msg, MSG_NOSIGNAL)) < 0) { + DPLOG(ERROR) << "sendmsg"; + return errno; + } + return 0; +} + +// static +bool UnixCredentialSocket::RecvMsg(int fd, + void* buf, + size_t buf_size, + ucred* creds, + std::vector* fds) { + iovec iov; + iov.iov_base = buf; + iov.iov_len = buf_size; + + msghdr msg = {}; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + char cmsg_buf[CMSG_SPACE(sizeof(ucred)) + + CMSG_SPACE(sizeof(int) * kMaxSendRecvMsgFDs)]; + msg.msg_control = cmsg_buf; + msg.msg_controllen = sizeof(cmsg_buf); + + int res = HANDLE_EINTR(recvmsg(fd, &msg, 0)); + if (res < 0) { + PLOG(ERROR) << "recvmsg"; + return false; + } + + ucred* local_creds = nullptr; + std::vector local_fds; + bool unhandled_cmsgs = false; + + for (cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); + cmsg; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { + int* fdp = reinterpret_cast(CMSG_DATA(cmsg)); + size_t fd_count = (reinterpret_cast(cmsg) + cmsg->cmsg_len - + reinterpret_cast(fdp)) / + sizeof(int); + DCHECK_LE(fd_count, kMaxSendRecvMsgFDs); + for (size_t index = 0; index < fd_count; ++index) { + if (fds) { + local_fds.emplace_back(fdp[index]); + } else if (IGNORE_EINTR(close(fdp[index])) != 0) { + PLOG(ERROR) << "close"; + } + } + continue; + } + + if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_CREDENTIALS) { + DCHECK(!local_creds); + local_creds = reinterpret_cast(CMSG_DATA(cmsg)); + continue; + } + + LOG(ERROR) << "unhandled cmsg " << cmsg->cmsg_level << ", " + << cmsg->cmsg_type; + unhandled_cmsgs = true; + } + + if (unhandled_cmsgs) { + return false; + } + + if (msg.msg_name != nullptr || msg.msg_namelen != 0) { + LOG(ERROR) << "unexpected msg name"; + return false; + } + + if (msg.msg_flags & MSG_TRUNC || msg.msg_flags & MSG_CTRUNC) { + LOG(ERROR) << "truncated msg"; + return false; + } + + // Credentials are missing from the message either when the recv socket wasn't + // configured with SO_PASSCRED or when all sending sockets have been closed. + // In the latter case, res == 0. This case is also indistinguishable from an + // empty message sent to a recv socket which hasn't set SO_PASSCRED. + if (!local_creds) { + LOG_IF(ERROR, res != 0) << "missing credentials"; + return false; + } + + if (static_cast(res) != buf_size) { + LOG(ERROR) << "incorrect payload size " << res; + return false; + } + + *creds = *local_creds; + if (fds) { + fds->swap(local_fds); + } + return true; +} + +} // namespace crashpad diff --git a/util/linux/socket.h b/util/linux/socket.h new file mode 100644 index 00000000..85860f7b --- /dev/null +++ b/util/linux/socket.h @@ -0,0 +1,96 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_LINUX_SOCKET_H_ +#define CRASHPAD_UTIL_LINUX_SOCKET_H_ + +#include +#include + +#include + +#include "base/macros.h" +#include "util/file/file_io.h" + +namespace crashpad { + +//! \brief Utilities for communicating over `SO_PASSCRED` enabled `AF_UNIX` +//! sockets. +class UnixCredentialSocket { + public: + //! \brief Creates an `AF_UNIX` family socket pair with `SO_PASSCRED` set on + //! each socket. + //! + //! \param[out] s1 One end of the connected pair. + //! \param[out] s2 The other end of the connected pair. + //! \return `true` on success. Otherwise, `false` with a message logged. + static bool CreateCredentialSocketpair(ScopedFileHandle* s1, + ScopedFileHandle* s2); + + //! \brief The maximum number of file descriptors that may be sent/received + //! with `SendMsg()` or `RecvMsg()`. + static const size_t kMaxSendRecvMsgFDs; + + //! \brief Wraps `sendmsg()` to send a message with file descriptors. + //! + //! This function is intended for use with `AF_UNIX` family sockets and + //! passes file descriptors with `SCM_RIGHTS`. + //! + //! This function may be used in a compromised context. + //! + //! \param[in] fd The file descriptor to write the message to. + //! \param[in] buf The buffer containing the message. + //! \param[in] buf_size The size of the message. + //! \param[in] fds An array of at most `kMaxSendRecvMsgFDs` file descriptors. + //! Optional. + //! \param[in] fd_count The number of file descriptors in \a fds. Required + //! only if \a fds was set. + //! \return 0 on success or an error code on failure. + static int SendMsg(int fd, + const void* buf, + size_t buf_size, + const int* fds = nullptr, + size_t fd_count = 0); + + //! \brief Wraps `recvmsg()` to receive a message with file descriptors and + //! credentials. + //! + //! This function is intended to be used with `AF_UNIX` family sockets. Up to + //! `kMaxSendRecvMsgFDs` file descriptors may be received (via `SCM_RIGHTS`). + //! The socket must have `SO_PASSCRED` set. + //! + //! \param[in] fd The file descriptor to receive the message on. + //! \param[out] buf The buffer to fill with the message. + //! \param[in] buf_size The size of the message. + //! \param[out] creds The credentials of the sender. + //! \param[out] fds The recieved file descriptors. Optional. If `nullptr`, all + //! received file descriptors will be closed. + //! \return `true` on success. Otherwise, `false`, with a message logged. No + //! message will be logged if the message was detected to be an EOF + //! condition triggered by all clients disconnecting. This case is + //! indistinguishable from misuses of this interface that haven't set + //! `SO_PASSCRED` on \a fd. + static bool RecvMsg(int fd, + void* buf, + size_t buf_size, + ucred* creds, + std::vector* fds = nullptr); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(UnixCredentialSocket); +}; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_LINUX_SOCKET_H_ diff --git a/util/linux/socket_test.cc b/util/linux/socket_test.cc new file mode 100644 index 00000000..4e583aed --- /dev/null +++ b/util/linux/socket_test.cc @@ -0,0 +1,139 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/linux/socket.h" + +#include "base/logging.h" +#include "base/macros.h" +#include "base/posix/eintr_wrapper.h" +#include "gtest/gtest.h" +#include "util/linux/socket.h" + +namespace crashpad { +namespace test { +namespace { + +TEST(Socket, Credentials) { + ScopedFileHandle send_sock, recv_sock; + ASSERT_TRUE( + UnixCredentialSocket::CreateCredentialSocketpair(&send_sock, &recv_sock)); + + char msg = 42; + ASSERT_EQ(UnixCredentialSocket::SendMsg(send_sock.get(), &msg, sizeof(msg)), + 0); + + char recv_msg = 0; + ucred creds; + ASSERT_TRUE(UnixCredentialSocket::RecvMsg( + recv_sock.get(), &recv_msg, sizeof(recv_msg), &creds)); + EXPECT_EQ(recv_msg, msg); + EXPECT_EQ(creds.pid, getpid()); + EXPECT_EQ(creds.uid, geteuid()); + EXPECT_EQ(creds.gid, getegid()); +} + +TEST(Socket, EmptyMessages) { + ScopedFileHandle send_sock, recv_sock; + ASSERT_TRUE( + UnixCredentialSocket::CreateCredentialSocketpair(&send_sock, &recv_sock)); + + ASSERT_EQ(UnixCredentialSocket::SendMsg(send_sock.get(), nullptr, 0), 0); + + ucred creds; + ASSERT_TRUE( + UnixCredentialSocket::RecvMsg(recv_sock.get(), nullptr, 0, &creds)); + EXPECT_EQ(creds.pid, getpid()); + EXPECT_EQ(creds.uid, geteuid()); + EXPECT_EQ(creds.gid, getegid()); +} + +TEST(Socket, Hangup) { + ScopedFileHandle send_sock, recv_sock; + ASSERT_TRUE( + UnixCredentialSocket::CreateCredentialSocketpair(&send_sock, &recv_sock)); + + send_sock.reset(); + + char recv_msg = 0; + ucred creds; + EXPECT_FALSE(UnixCredentialSocket::RecvMsg( + recv_sock.get(), &recv_msg, sizeof(recv_msg), &creds)); +} + +TEST(Socket, FileDescriptors) { + ScopedFileHandle send_sock, recv_sock; + ASSERT_TRUE( + UnixCredentialSocket::CreateCredentialSocketpair(&send_sock, &recv_sock)); + + ScopedFileHandle test_fd1, test_fd2; + ASSERT_TRUE( + UnixCredentialSocket::CreateCredentialSocketpair(&test_fd1, &test_fd2)); + + char msg = 42; + ASSERT_EQ(UnixCredentialSocket::SendMsg( + send_sock.get(), &msg, sizeof(msg), &test_fd1.get(), 1), + 0); + + char recv_msg = 0; + ucred creds; + std::vector fds; + ASSERT_TRUE(UnixCredentialSocket::RecvMsg( + recv_sock.get(), &recv_msg, sizeof(recv_msg), &creds, &fds)); + ASSERT_EQ(fds.size(), 1u); +} + +TEST(Socket, RecvClosesFileDescriptors) { + ScopedFileHandle send_sock, recv_sock; + ASSERT_TRUE( + UnixCredentialSocket::CreateCredentialSocketpair(&send_sock, &recv_sock)); + + ScopedFileHandle send_fds[UnixCredentialSocket::kMaxSendRecvMsgFDs]; + ScopedFileHandle recv_fds[UnixCredentialSocket::kMaxSendRecvMsgFDs]; + int raw_recv_fds[UnixCredentialSocket::kMaxSendRecvMsgFDs]; + for (size_t index = 0; index < UnixCredentialSocket::kMaxSendRecvMsgFDs; + ++index) { + ASSERT_TRUE(UnixCredentialSocket::CreateCredentialSocketpair( + &send_fds[index], &recv_fds[index])); + raw_recv_fds[index] = recv_fds[index].get(); + } + + char msg = 42; + ASSERT_EQ( + UnixCredentialSocket::SendMsg(send_sock.get(), + &msg, + sizeof(msg), + raw_recv_fds, + UnixCredentialSocket::kMaxSendRecvMsgFDs), + 0); + + char recv_msg = 0; + ucred creds; + ASSERT_TRUE(UnixCredentialSocket::RecvMsg( + recv_sock.get(), &recv_msg, sizeof(recv_msg), &creds)); + EXPECT_EQ(creds.pid, getpid()); + + for (size_t index = 0; index < UnixCredentialSocket::kMaxSendRecvMsgFDs; + ++index) { + recv_fds[index].reset(); + char c; + EXPECT_EQ( + HANDLE_EINTR(send(send_fds[index].get(), &c, sizeof(c), MSG_NOSIGNAL)), + -1); + EXPECT_EQ(errno, EPIPE); + } +} + +} // namespace +} // namespace test +} // namespace crashpad diff --git a/util/mac/checked_mach_address_range_test.cc b/util/mac/checked_mach_address_range_test.cc index 089f6815..85313795 100644 --- a/util/mac/checked_mach_address_range_test.cc +++ b/util/mac/checked_mach_address_range_test.cc @@ -19,7 +19,7 @@ #include -#include "base/macros.h" +#include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "build/build_config.h" #include "gtest/gtest.h" @@ -116,7 +116,7 @@ TEST(CheckedMachAddressRange, IsValid) { {0xffffffffffffffff, 1, kInvalid}, }; - for (size_t index = 0; index < arraysize(kTestData); ++index) { + for (size_t index = 0; index < base::size(kTestData); ++index) { const auto& testcase = kTestData[index]; SCOPED_TRACE(base::StringPrintf("index %zu, base 0x%llx, size 0x%llx", index, @@ -166,7 +166,7 @@ TEST(CheckedMachAddressRange, ContainsValue) { CheckedMachAddressRange parent_range_32(false, 0x2000, 0x1000); ASSERT_TRUE(parent_range_32.IsValid()); - for (size_t index = 0; index < arraysize(kTestData); ++index) { + for (size_t index = 0; index < base::size(kTestData); ++index) { const auto& testcase = kTestData[index]; SCOPED_TRACE( base::StringPrintf("index %zu, value 0x%llx", index, testcase.value)); @@ -223,7 +223,7 @@ TEST(CheckedMachAddressRange, ContainsRange) { CheckedMachAddressRange parent_range_32(false, 0x2000, 0x1000); ASSERT_TRUE(parent_range_32.IsValid()); - for (size_t index = 0; index < arraysize(kTestData); ++index) { + for (size_t index = 0; index < base::size(kTestData); ++index) { const auto& testcase = kTestData[index]; SCOPED_TRACE(base::StringPrintf("index %zu, base 0x%llx, size 0x%llx", index, diff --git a/util/mac/launchd_test.mm b/util/mac/launchd_test.mm index 12ed8320..b793a9b9 100644 --- a/util/mac/launchd_test.mm +++ b/util/mac/launchd_test.mm @@ -23,7 +23,7 @@ #include #include "base/mac/scoped_launch_data.h" -#include "base/macros.h" +#include "base/stl_util.h" #include "gtest/gtest.h" #include "util/stdlib/objc.h" @@ -58,7 +58,7 @@ TEST(Launchd, CFPropertyToLaunchData_Integer) { @0xfedcba9876543210, }; - for (size_t index = 0; index < arraysize(integer_nses); ++index) { + for (size_t index = 0; index < base::size(integer_nses); ++index) { NSNumber* integer_ns = integer_nses[index]; launch_data.reset(CFPropertyToLaunchData(integer_ns)); ASSERT_TRUE(launch_data.get()); @@ -88,7 +88,7 @@ TEST(Launchd, CFPropertyToLaunchData_FloatingPoint) { [NSNumber numberWithDouble:std::numeric_limits::signaling_NaN()], }; - for (size_t index = 0; index < arraysize(double_nses); ++index) { + for (size_t index = 0; index < base::size(double_nses); ++index) { NSNumber* double_ns = double_nses[index]; launch_data.reset(CFPropertyToLaunchData(double_ns)); ASSERT_TRUE(launch_data.get()); @@ -114,7 +114,7 @@ TEST(Launchd, CFPropertyToLaunchData_Boolean) { @YES, }; - for (size_t index = 0; index < arraysize(bool_nses); ++index) { + for (size_t index = 0; index < base::size(bool_nses); ++index) { NSNumber* bool_ns = bool_nses[index]; launch_data.reset(CFPropertyToLaunchData(bool_ns)); ASSERT_TRUE(launch_data.get()); @@ -138,7 +138,7 @@ TEST(Launchd, CFPropertyToLaunchData_String) { @"Üñîçø∂é", }; - for (size_t index = 0; index < arraysize(string_nses); ++index) { + for (size_t index = 0; index < base::size(string_nses); ++index) { NSString* string_ns = string_nses[index]; launch_data.reset(CFPropertyToLaunchData(string_ns)); ASSERT_TRUE(launch_data.get()); diff --git a/util/mac/xattr.cc b/util/mac/xattr.cc index 75d7938d..c63fc87c 100644 --- a/util/mac/xattr.cc +++ b/util/mac/xattr.cc @@ -20,7 +20,6 @@ #include #include "base/logging.h" -#include "base/macros.h" #include "base/numerics/safe_conversions.h" #include "base/strings/string_number_conversions.h" #include "base/strings/stringprintf.h" diff --git a/util/mach/bootstrap.cc b/util/mach/bootstrap.cc new file mode 100644 index 00000000..f5534372 --- /dev/null +++ b/util/mach/bootstrap.cc @@ -0,0 +1,109 @@ +// Copyright 2020 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/mach/bootstrap.h" + +#include +#include + +#include "base/mac/mach_logging.h" + +namespace { + +// This forms the internal implementation for BootstrapCheckIn() and +// BootstrapLookUp(), which follow the same logic aside from the routine called +// and the right type returned. + +struct BootstrapCheckInTraits { + using Type = base::mac::ScopedMachReceiveRight; + static kern_return_t Call(mach_port_t bootstrap_port, + const char* service_name, + mach_port_t* service_port) { + return bootstrap_check_in(bootstrap_port, service_name, service_port); + } + static constexpr char kName[] = "bootstrap_check_in"; +}; +constexpr char BootstrapCheckInTraits::kName[]; + +struct BootstrapLookUpTraits { + using Type = base::mac::ScopedMachSendRight; + static kern_return_t Call(mach_port_t bootstrap_port, + const char* service_name, + mach_port_t* service_port) { + return bootstrap_look_up(bootstrap_port, service_name, service_port); + } + static constexpr char kName[] = "bootstrap_look_up"; +}; +constexpr char BootstrapLookUpTraits::kName[]; + +template +typename Traits::Type BootstrapCheckInOrLookUp( + const std::string& service_name) { + // bootstrap_check_in() and bootstrap_look_up() silently truncate service + // names longer than BOOTSTRAP_MAX_NAME_LEN. This check ensures that the name + // will not be truncated. + if (service_name.size() >= BOOTSTRAP_MAX_NAME_LEN) { + LOG(ERROR) << Traits::kName << " " << service_name << ": name too long"; + return typename Traits::Type(MACH_PORT_NULL); + } + + mach_port_t service_port; + kern_return_t kr = + Traits::Call(bootstrap_port, service_name.c_str(), &service_port); + if (kr != BOOTSTRAP_SUCCESS) { + BOOTSTRAP_LOG(ERROR, kr) << Traits::kName << " " << service_name; + service_port = MACH_PORT_NULL; + } + + return typename Traits::Type(service_port); +} + +} // namespace + +namespace crashpad { + +base::mac::ScopedMachReceiveRight BootstrapCheckIn( + const std::string& service_name) { + return BootstrapCheckInOrLookUp(service_name); +} + +base::mac::ScopedMachSendRight BootstrapLookUp( + const std::string& service_name) { + base::mac::ScopedMachSendRight send( + BootstrapCheckInOrLookUp(service_name)); + + // It’s possible to race the bootstrap server when the receive right + // corresponding to the looked-up send right is destroyed immediately before + // the bootstrap_look_up() call. If the bootstrap server believes that + // |service_name| is still registered before processing the port-destroyed + // notification sent to it by the kernel, it will respond to a + // bootstrap_look_up() request with a send right that has become a dead name, + // which will be returned to the bootstrap_look_up() caller, translated into + // the caller’s IPC port name space, as the special MACH_PORT_DEAD port name. + // Check for that and return MACH_PORT_NULL in its place, as though the + // bootstrap server had fully processed the port-destroyed notification before + // responding to bootstrap_look_up(). + if (send.get() == MACH_PORT_DEAD) { + LOG(ERROR) << "bootstrap_look_up " << service_name << ": service is dead"; + send.reset(); + } + + return send; +} + +base::mac::ScopedMachSendRight SystemCrashReporterHandler() { + return BootstrapLookUp("com.apple.ReportCrash"); +} + +} // namespace crashpad diff --git a/util/mach/bootstrap.h b/util/mach/bootstrap.h new file mode 100644 index 00000000..680f5b91 --- /dev/null +++ b/util/mach/bootstrap.h @@ -0,0 +1,63 @@ +// Copyright 2020 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_MACH_BOOTSTRAP_H_ +#define CRASHPAD_UTIL_MACH_BOOTSTRAP_H_ + +#include + +#include "base/mac/scoped_mach_port.h" + +namespace crashpad { + +//! \brief Makes a `boostrap_check_in()` call to the process’ bootstrap server. +//! +//! This function is provided to make it easier to call `bootstrap_check_in()` +//! while avoiding accidental leaks of the returned receive right. +//! +//! \param[in] service_name The service name to check in. +//! +//! \return On success, a receive right to the service port. On failure, +//! `MACH_PORT_NULL` with a message logged. +base::mac::ScopedMachReceiveRight BootstrapCheckIn( + const std::string& service_name); + +//! \brief Makes a `boostrap_look_up()` call to the process’ bootstrap server. +//! +//! This function is provided to make it easier to call `bootstrap_look_up()` +//! while avoiding accidental leaks of the returned send right. +//! +//! \param[in] service_name The service name to look up. +//! +//! \return On success, a send right to the service port. On failure, +//! `MACH_PORT_NULL` with a message logged. +base::mac::ScopedMachSendRight BootstrapLookUp(const std::string& service_name); + +//! \brief Obtains the system’s default Mach exception handler for crash-type +//! exceptions. +//! +//! This is obtained by looking up `"com.apple.ReportCrash"` with the bootstrap +//! server. The service name comes from the first launch agent loaded by +//! `launchd` with a `MachServices` entry having `ExceptionServer` set. This +//! launch agent is normally loaded from +//! `/System/Library/LaunchAgents/com.apple.ReportCrash.plist`. +//! +//! \return On success, a send right to an `exception_handler_t` corresponding +//! to the system’s default crash reporter. On failure, `MACH_PORT_NULL`, +//! with a message logged. +base::mac::ScopedMachSendRight SystemCrashReporterHandler(); + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_MACH_BOOTSTRAP_H_ diff --git a/util/mach/bootstrap_test.cc b/util/mach/bootstrap_test.cc new file mode 100644 index 00000000..44587d08 --- /dev/null +++ b/util/mach/bootstrap_test.cc @@ -0,0 +1,70 @@ +// Copyright 2020 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/mach/bootstrap.h" + +#include "base/mac/scoped_mach_port.h" +#include "gtest/gtest.h" +#include "util/mach/mach_extensions.h" +#include "util/misc/random_string.h" + +namespace crashpad { +namespace test { +namespace { + +TEST(Bootstrap, BootstrapCheckInAndLookUp) { + // This should always exist. + base::mac::ScopedMachSendRight report_crash( + BootstrapLookUp("com.apple.ReportCrash")); + EXPECT_NE(report_crash, kMachPortNull); + + std::string service_name = "org.chromium.crashpad.test.bootstrap_check_in."; + service_name.append(RandomString()); + + { + // The new service hasn’t checked in yet, so this should fail. + base::mac::ScopedMachSendRight send(BootstrapLookUp(service_name)); + EXPECT_EQ(send, kMachPortNull); + + // Check it in. + base::mac::ScopedMachReceiveRight receive(BootstrapCheckIn(service_name)); + EXPECT_NE(receive, kMachPortNull); + + // Now it should be possible to look up the new service. + send = BootstrapLookUp(service_name); + EXPECT_NE(send, kMachPortNull); + + // It shouldn’t be possible to check the service in while it’s active. + base::mac::ScopedMachReceiveRight receive_2(BootstrapCheckIn(service_name)); + EXPECT_EQ(receive_2, kMachPortNull); + } + + // The new service should be gone now. + base::mac::ScopedMachSendRight send(BootstrapLookUp(service_name)); + EXPECT_EQ(send, kMachPortNull); + + // It should be possible to check it in again. + base::mac::ScopedMachReceiveRight receive(BootstrapCheckIn(service_name)); + EXPECT_NE(receive, kMachPortNull); +} + +TEST(Bootstrap, SystemCrashReporterHandler) { + base::mac::ScopedMachSendRight system_crash_reporter_handler( + SystemCrashReporterHandler()); + EXPECT_TRUE(system_crash_reporter_handler.is_valid()); +} + +} // namespace +} // namespace test +} // namespace crashpad diff --git a/util/mach/child_port_handshake.cc b/util/mach/child_port_handshake.cc index 750d3cbe..3ff9828b 100644 --- a/util/mach/child_port_handshake.cc +++ b/util/mach/child_port_handshake.cc @@ -31,8 +31,10 @@ #include "base/mac/scoped_mach_port.h" #include "base/posix/eintr_wrapper.h" #include "base/rand_util.h" +#include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "util/file/file_io.h" +#include "util/mach/bootstrap.h" #include "util/mach/child_port.h" #include "util/mach/child_port_server.h" #include "util/mach/mach_extensions.h" @@ -125,8 +127,12 @@ mach_port_t ChildPortHandshakeServer::RunServer( return MACH_PORT_NULL; } - // A kqueue cannot monitor a raw Mach receive right with EVFILT_MACHPORT. It - // requires a port set. Create a new port set and add the receive right to it. + // Prior to macOS 10.12, a kqueue cannot monitor a raw Mach receive right with + // EVFILT_MACHPORT. It requires a port set. Compare 10.11.6 + // xnu-3248.60.10/osfmk/ipc/ipc_pset.c filt_machportattach(), which requires + // MACH_PORT_RIGHT_PORT_SET, to 10.12.0 xnu-3789.1.32/osfmk/ipc/ipc_pset.c + // filt_machportattach(), which also handles MACH_PORT_TYPE_RECEIVE. Create a + // new port set and add the receive right to it. base::mac::ScopedMachPortSet server_port_set( NewMachPort(MACH_PORT_RIGHT_PORT_SET)); CHECK(server_port_set.is_valid()); @@ -157,8 +163,8 @@ mach_port_t ChildPortHandshakeServer::RunServer( 0, 0, nullptr); - int rv = HANDLE_EINTR( - kevent(kq.get(), changelist, arraysize(changelist), nullptr, 0, nullptr)); + int rv = HANDLE_EINTR(kevent( + kq.get(), changelist, base::size(changelist), nullptr, 0, nullptr)); PCHECK(rv != -1) << "kevent"; ChildPortServer child_port_server(this); diff --git a/util/mach/child_port_server.cc b/util/mach/child_port_server.cc index a3223028..aa7a1b8b 100644 --- a/util/mach/child_port_server.cc +++ b/util/mach/child_port_server.cc @@ -15,6 +15,7 @@ #include "util/mach/child_port_server.h" #include "base/logging.h" +#include "base/stl_util.h" #include "util/mach/child_portServer.h" #include "util/mach/mach_message.h" @@ -90,7 +91,7 @@ std::set ChildPortServer::MachMessageServerRequestIDs() { static constexpr mach_msg_id_t request_ids[] = {kMachMessageIDChildPortCheckIn}; return std::set(&request_ids[0], - &request_ids[arraysize(request_ids)]); + &request_ids[base::size(request_ids)]); } mach_msg_size_t ChildPortServer::MachMessageServerRequestSize() { diff --git a/util/mach/composite_mach_message_server_test.cc b/util/mach/composite_mach_message_server_test.cc index d45eca0b..87242be3 100644 --- a/util/mach/composite_mach_message_server_test.cc +++ b/util/mach/composite_mach_message_server_test.cc @@ -16,6 +16,7 @@ #include +#include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "gtest/gtest.h" #include "test/gtest_death.h" @@ -196,7 +197,7 @@ TEST(CompositeMachMessageServer, ThreeHandlers) { TestMachMessageHandler handlers[3]; std::set expect_request_ids; - for (size_t index = 0; index < arraysize(kRequestIDs0); ++index) { + for (size_t index = 0; index < base::size(kRequestIDs0); ++index) { const mach_msg_id_t request_id = kRequestIDs0[index]; handlers[0].AddRequestID(request_id); expect_request_ids.insert(request_id); @@ -205,7 +206,7 @@ TEST(CompositeMachMessageServer, ThreeHandlers) { handlers[0].SetReplySize(sizeof(mig_reply_error_t)); handlers[0].SetReturnCodes(true, kReturnCode0, false); - for (size_t index = 0; index < arraysize(kRequestIDs1); ++index) { + for (size_t index = 0; index < base::size(kRequestIDs1); ++index) { const mach_msg_id_t request_id = kRequestIDs1[index]; handlers[1].AddRequestID(request_id); expect_request_ids.insert(request_id); @@ -214,7 +215,7 @@ TEST(CompositeMachMessageServer, ThreeHandlers) { handlers[1].SetReplySize(200); handlers[1].SetReturnCodes(false, kReturnCode1, true); - for (size_t index = 0; index < arraysize(kRequestIDs2); ++index) { + for (size_t index = 0; index < base::size(kRequestIDs2); ++index) { const mach_msg_id_t request_id = kRequestIDs2[index]; handlers[2].AddRequestID(request_id); expect_request_ids.insert(request_id); @@ -252,7 +253,7 @@ TEST(CompositeMachMessageServer, ThreeHandlers) { // Send messages with known request IDs. - for (size_t index = 0; index < arraysize(kRequestIDs0); ++index) { + for (size_t index = 0; index < base::size(kRequestIDs0); ++index) { request.header.msgh_id = kRequestIDs0[index]; SCOPED_TRACE(base::StringPrintf( "handler 0, index %zu, id %d", index, request.header.msgh_id)); @@ -263,7 +264,7 @@ TEST(CompositeMachMessageServer, ThreeHandlers) { EXPECT_FALSE(destroy_complex_request); } - for (size_t index = 0; index < arraysize(kRequestIDs1); ++index) { + for (size_t index = 0; index < base::size(kRequestIDs1); ++index) { request.header.msgh_id = kRequestIDs1[index]; SCOPED_TRACE(base::StringPrintf( "handler 1, index %zu, id %d", index, request.header.msgh_id)); @@ -274,7 +275,7 @@ TEST(CompositeMachMessageServer, ThreeHandlers) { EXPECT_TRUE(destroy_complex_request); } - for (size_t index = 0; index < arraysize(kRequestIDs2); ++index) { + for (size_t index = 0; index < base::size(kRequestIDs2); ++index) { request.header.msgh_id = kRequestIDs2[index]; SCOPED_TRACE(base::StringPrintf( "handler 2, index %zu, id %d", index, request.header.msgh_id)); diff --git a/util/mach/exc_client_variants_test.cc b/util/mach/exc_client_variants_test.cc index b7288563..007442ab 100644 --- a/util/mach/exc_client_variants_test.cc +++ b/util/mach/exc_client_variants_test.cc @@ -20,6 +20,7 @@ #include #include "base/macros.h" +#include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "gtest/gtest.h" #include "test/mac/mach_errors.h" @@ -181,11 +182,11 @@ class TestExcClientVariants : public MachMultiprocess, // These aren’t real flavors, it’s just for testing. flavor = exception_ + 10; flavor_p = &flavor; - for (size_t index = 0; index < arraysize(old_state); ++index) { + for (size_t index = 0; index < base::size(old_state); ++index) { old_state[index] = index; } old_state_p = reinterpret_cast(&old_state); - old_state_count = arraysize(old_state); + old_state_count = base::size(old_state); // new_state and new_state_count are out parameters that the server should // never see or use, so set them to bogus values. The call to the server @@ -202,7 +203,7 @@ class TestExcClientVariants : public MachMultiprocess, task, exception, code, - arraysize(code), + base::size(code), flavor_p, old_state_p, old_state_count, @@ -273,7 +274,7 @@ TEST(ExcClientVariants, UniversalExceptionRaise) { kMachExceptionCodes | EXCEPTION_STATE_IDENTITY, }; - for (size_t index = 0; index < arraysize(kBehaviors); ++index) { + for (size_t index = 0; index < base::size(kBehaviors); ++index) { exception_behavior_t behavior = kBehaviors[index]; SCOPED_TRACE(base::StringPrintf("index %zu, behavior %d", index, behavior)); diff --git a/util/mach/exc_server_variants.cc b/util/mach/exc_server_variants.cc index e7174ac7..c272ae72 100644 --- a/util/mach/exc_server_variants.cc +++ b/util/mach/exc_server_variants.cc @@ -21,6 +21,8 @@ #include #include "base/logging.h" +#include "base/stl_util.h" +#include "build/build_config.h" #include "util/mac/mac_util.h" #include "util/mach/composite_mach_message_server.h" #include "util/mach/exc.h" @@ -242,7 +244,7 @@ class ExcServer : public MachMessageServer::Interface { Traits::kMachMessageIDExceptionRaiseStateIdentity, }; return std::set(&request_ids[0], - &request_ids[arraysize(request_ids)]); + &request_ids[base::size(request_ids)]); } mach_msg_size_t MachMessageServerRequestSize() override { @@ -319,7 +321,7 @@ bool ExcServer::MachMessageServerFunction( using Reply = typename Traits::ExceptionRaiseStateReply; Reply* out_reply = reinterpret_cast(out_header); out_reply->flavor = in_request_1->flavor; - out_reply->new_stateCnt = arraysize(out_reply->new_state); + out_reply->new_stateCnt = base::size(out_reply->new_state); out_reply->RetCode = interface_->CatchExceptionRaiseState(in_header->msgh_local_port, in_request->exception, @@ -362,7 +364,7 @@ bool ExcServer::MachMessageServerFunction( using Reply = typename Traits::ExceptionRaiseStateIdentityReply; Reply* out_reply = reinterpret_cast(out_header); out_reply->flavor = in_request_1->flavor; - out_reply->new_stateCnt = arraysize(out_reply->new_state); + out_reply->new_stateCnt = base::size(out_reply->new_state); out_reply->RetCode = interface_->CatchExceptionRaiseStateIdentity( in_header->msgh_local_port, in_request->thread.name, @@ -681,7 +683,7 @@ kern_return_t ExcServerSuccessfulReturnValue(exception_type_t exception, exception_behavior_t behavior, bool set_thread_state) { if (exception == EXC_CRASH -#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_11 +#if !defined(OS_IOS) && MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_11 && MacOSXMinorVersion() >= 11 #endif ) { diff --git a/util/mach/exc_server_variants_test.cc b/util/mach/exc_server_variants_test.cc index 5e67bfe3..bea3d767 100644 --- a/util/mach/exc_server_variants_test.cc +++ b/util/mach/exc_server_variants_test.cc @@ -19,18 +19,22 @@ #include #include +#include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "build/build_config.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include "test/mac/mach_errors.h" -#include "test/mac/mach_multiprocess.h" #include "util/mac/mac_util.h" #include "util/mach/exception_behaviors.h" #include "util/mach/exception_types.h" #include "util/mach/mach_message.h" #include "util/misc/implicit_cast.h" +#if !defined(OS_IOS) +#include "test/mac/mach_multiprocess.h" +#endif // !OS_IOS + namespace crashpad { namespace test { namespace { @@ -228,7 +232,7 @@ struct __attribute__((packed, aligned(4))) ExceptionRaiseStateReply { EXPECT_EQ(memcmp(&NDR, &NDR_record, sizeof(NDR)), 0); EXPECT_EQ(RetCode, KERN_SUCCESS); EXPECT_EQ(flavor, kThreadStateFlavor); - EXPECT_EQ(new_stateCnt, arraysize(new_state)); + EXPECT_EQ(new_stateCnt, base::size(new_state)); } mach_msg_header_t Head; @@ -659,7 +663,7 @@ TEST(ExcServerVariants, MockExceptionRaiseState) { AreExceptionCodes(kTestExceptonCodes[0], kTestExceptonCodes[1]), Pointee(Eq(kThreadStateFlavor)), IsThreadStateAndCount(kThreadStateFlavorCount), - IsThreadStateAndCount(arraysize(reply.new_state)), + IsThreadStateAndCount(base::size(reply.new_state)), Eq(request.Trailer()))) .WillOnce(Return(KERN_SUCCESS)) .RetiresOnSaturation(); @@ -708,7 +712,7 @@ TEST(ExcServerVariants, MockExceptionRaiseStateIdentity) { AreExceptionCodes(kTestExceptonCodes[0], kTestExceptonCodes[1]), Pointee(Eq(kThreadStateFlavor)), IsThreadStateAndCount(kThreadStateFlavorCount), - IsThreadStateAndCount(arraysize(reply.new_state)), + IsThreadStateAndCount(base::size(reply.new_state)), Eq(request.Trailer()))) .WillOnce(Return(KERN_SUCCESS)) .RetiresOnSaturation(); @@ -802,7 +806,7 @@ TEST(ExcServerVariants, MockMachExceptionRaiseState) { kTestMachExceptionCodes[1]), Pointee(Eq(kThreadStateFlavor)), IsThreadStateAndCount(kThreadStateFlavorCount), - IsThreadStateAndCount(arraysize(reply.new_state)), + IsThreadStateAndCount(base::size(reply.new_state)), Eq(request.Trailer()))) .WillOnce(Return(KERN_SUCCESS)) .RetiresOnSaturation(); @@ -852,7 +856,7 @@ TEST(ExcServerVariants, MockMachExceptionRaiseStateIdentity) { kTestMachExceptionCodes[1]), Pointee(Eq(kThreadStateFlavor)), IsThreadStateAndCount(kThreadStateFlavorCount), - IsThreadStateAndCount(arraysize(reply.new_state)), + IsThreadStateAndCount(base::size(reply.new_state)), Eq(request.Trailer()))) .WillOnce(Return(KERN_SUCCESS)) .RetiresOnSaturation(); @@ -906,7 +910,7 @@ TEST(ExcServerVariants, MockUnknownID) { 2508, }; - for (size_t index = 0; index < arraysize(unknown_ids); ++index) { + for (size_t index = 0; index < base::size(unknown_ids); ++index) { mach_msg_id_t id = unknown_ids[index]; SCOPED_TRACE(base::StringPrintf("unknown id %d", id)); @@ -957,6 +961,8 @@ TEST(ExcServerVariants, MachMessageServerRequestIDs) { expect_request_ids); } +#if !defined(OS_IOS) + class TestExcServerVariants : public MachMultiprocess, public UniversalMachExcServer::Interface { public: @@ -1179,7 +1185,7 @@ TEST(ExcServerVariants, ThreadStates) { #endif }; - for (size_t index = 0; index < arraysize(test_data); ++index) { + for (size_t index = 0; index < base::size(test_data); ++index) { const auto& test = test_data[index]; SCOPED_TRACE( base::StringPrintf("index %zu, flavor %d", index, test.flavor)); @@ -1192,9 +1198,16 @@ TEST(ExcServerVariants, ThreadStates) { } } +#endif // !OS_IOS + TEST(ExcServerVariants, ExcServerSuccessfulReturnValue) { +#if defined(OS_IOS) + // iOS 9 ≅ OS X 10.11. + const kern_return_t prefer_not_set_thread_state = KERN_SUCCESS; +#else const kern_return_t prefer_not_set_thread_state = MacOSXMinorVersion() < 11 ? MACH_RCV_PORT_DIED : KERN_SUCCESS; +#endif const struct { exception_type_t exception; @@ -1252,7 +1265,7 @@ TEST(ExcServerVariants, ExcServerSuccessfulReturnValue) { KERN_SUCCESS}, }; - for (size_t index = 0; index < arraysize(kTestData); ++index) { + for (size_t index = 0; index < base::size(kTestData); ++index) { const auto& test_data = kTestData[index]; SCOPED_TRACE( base::StringPrintf("index %zu, behavior %d, set_thread_state %s", @@ -1271,8 +1284,8 @@ TEST(ExcServerVariants, ExcServerCopyState) { static constexpr natural_t old_state[] = {1, 2, 3, 4, 5}; natural_t new_state[10] = {}; - constexpr mach_msg_type_number_t old_state_count = arraysize(old_state); - mach_msg_type_number_t new_state_count = arraysize(new_state); + constexpr mach_msg_type_number_t old_state_count = base::size(old_state); + mach_msg_type_number_t new_state_count = base::size(new_state); // EXCEPTION_DEFAULT (with or without MACH_EXCEPTION_CODES) is not // state-carrying. new_state and new_state_count should be untouched. @@ -1281,8 +1294,8 @@ TEST(ExcServerVariants, ExcServerCopyState) { old_state_count, new_state, &new_state_count); - EXPECT_EQ(new_state_count, arraysize(new_state)); - for (size_t i = 0; i < arraysize(new_state); ++i) { + EXPECT_EQ(new_state_count, base::size(new_state)); + for (size_t i = 0; i < base::size(new_state); ++i) { EXPECT_EQ(new_state[i], 0u) << "i " << i; } @@ -1291,8 +1304,8 @@ TEST(ExcServerVariants, ExcServerCopyState) { old_state_count, new_state, &new_state_count); - EXPECT_EQ(new_state_count, arraysize(new_state)); - for (size_t i = 0; i < arraysize(new_state); ++i) { + EXPECT_EQ(new_state_count, base::size(new_state)); + for (size_t i = 0; i < base::size(new_state); ++i) { EXPECT_EQ(new_state[i], 0u) << "i " << i; } @@ -1304,7 +1317,7 @@ TEST(ExcServerVariants, ExcServerCopyState) { for (size_t i = 0; i < copy_limit; ++i) { EXPECT_EQ(new_state[i], old_state[i]) << "i " << i; } - for (size_t i = copy_limit; i < arraysize(new_state); ++i) { + for (size_t i = copy_limit; i < base::size(new_state); ++i) { EXPECT_EQ(new_state[i], 0u) << "i " << i; } @@ -1320,23 +1333,23 @@ TEST(ExcServerVariants, ExcServerCopyState) { for (size_t i = 0; i < copy_limit; ++i) { EXPECT_EQ(new_state[i], old_state[i]) << "i " << i; } - for (size_t i = copy_limit; i < arraysize(new_state); ++i) { + for (size_t i = copy_limit; i < base::size(new_state); ++i) { EXPECT_EQ(new_state[i], 0u) << "i " << i; } // This is a state-carrying exception where all of old_state is copied to // new_state, which is large enough to receive it and then some. - new_state_count = arraysize(new_state); + new_state_count = base::size(new_state); ExcServerCopyState(MACH_EXCEPTION_CODES | EXCEPTION_STATE_IDENTITY, old_state, old_state_count, new_state, &new_state_count); EXPECT_EQ(new_state_count, old_state_count); - for (size_t i = 0; i < arraysize(old_state); ++i) { + for (size_t i = 0; i < base::size(old_state); ++i) { EXPECT_EQ(new_state[i], old_state[i]) << "i " << i; } - for (size_t i = arraysize(old_state); i < arraysize(new_state); ++i) { + for (size_t i = base::size(old_state); i < base::size(new_state); ++i) { EXPECT_EQ(new_state[i], 0u) << "i " << i; } } diff --git a/util/mach/exception_behaviors_test.cc b/util/mach/exception_behaviors_test.cc index d45110d9..bdbf673a 100644 --- a/util/mach/exception_behaviors_test.cc +++ b/util/mach/exception_behaviors_test.cc @@ -16,7 +16,7 @@ #include -#include "base/macros.h" +#include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "gtest/gtest.h" #include "util/mach/mach_extensions.h" @@ -53,7 +53,7 @@ TEST(ExceptionBehaviors, ExceptionBehaviors) { EXCEPTION_STATE_IDENTITY}, }; - for (size_t index = 0; index < arraysize(kTestData); ++index) { + for (size_t index = 0; index < base::size(kTestData); ++index) { const auto& test_data = kTestData[index]; SCOPED_TRACE(base::StringPrintf( "index %zu, behavior %d", index, test_data.behavior)); diff --git a/util/mach/exception_types_test.cc b/util/mach/exception_types_test.cc index c24428a2..da2c8222 100644 --- a/util/mach/exception_types_test.cc +++ b/util/mach/exception_types_test.cc @@ -20,7 +20,7 @@ #include #include -#include "base/macros.h" +#include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "gtest/gtest.h" #include "util/mac/mac_util.h" @@ -67,7 +67,7 @@ TEST(ExceptionTypes, ExcCrashRecoverOriginalException) { {0, 0, 0, 0}, }; - for (size_t index = 0; index < arraysize(kTestData); ++index) { + for (size_t index = 0; index < base::size(kTestData); ++index) { const auto& test_data = kTestData[index]; SCOPED_TRACE(base::StringPrintf( "index %zu, code_0 0x%llx", index, test_data.code_0)); @@ -84,7 +84,7 @@ TEST(ExceptionTypes, ExcCrashRecoverOriginalException) { // Now make sure that ExcCrashRecoverOriginalException() properly ignores // optional arguments. - static_assert(arraysize(kTestData) >= 1, "must have something to test"); + static_assert(base::size(kTestData) >= 1, "must have something to test"); const auto& test_data = kTestData[0]; EXPECT_EQ( ExcCrashRecoverOriginalException(test_data.code_0, nullptr, nullptr), @@ -238,7 +238,7 @@ TEST(ExceptionTypes, ExceptionCodeForMetrics) { {0x00010000, 0x00010000, static_cast(0xffffffff)}, }; - for (size_t index = 0; index < arraysize(kTestData); ++index) { + for (size_t index = 0; index < base::size(kTestData); ++index) { const auto& test_data = kTestData[index]; SCOPED_TRACE(base::StringPrintf("index %zu, exception 0x%x, code_0 0x%llx", index, diff --git a/util/mach/mach_extensions.cc b/util/mach/mach_extensions.cc index 5450ab28..cefb9bf3 100644 --- a/util/mach/mach_extensions.cc +++ b/util/mach/mach_extensions.cc @@ -14,66 +14,14 @@ #include "util/mach/mach_extensions.h" +#include #include #include -#include #include "base/mac/mach_logging.h" +#include "build/build_config.h" #include "util/mac/mac_util.h" -namespace { - -// This forms the internal implementation for BootstrapCheckIn() and -// BootstrapLookUp(), which follow the same logic aside from the routine called -// and the right type returned. - -struct BootstrapCheckInTraits { - using Type = base::mac::ScopedMachReceiveRight; - static kern_return_t Call(mach_port_t bootstrap_port, - const char* service_name, - mach_port_t* service_port) { - return bootstrap_check_in(bootstrap_port, service_name, service_port); - } - static constexpr char kName[] = "bootstrap_check_in"; -}; -constexpr char BootstrapCheckInTraits::kName[]; - -struct BootstrapLookUpTraits { - using Type = base::mac::ScopedMachSendRight; - static kern_return_t Call(mach_port_t bootstrap_port, - const char* service_name, - mach_port_t* service_port) { - return bootstrap_look_up(bootstrap_port, service_name, service_port); - } - static constexpr char kName[] = "bootstrap_look_up"; -}; -constexpr char BootstrapLookUpTraits::kName[]; - -template -typename Traits::Type BootstrapCheckInOrLookUp( - const std::string& service_name) { - // bootstrap_check_in() and bootstrap_look_up() silently truncate service - // names longer than BOOTSTRAP_MAX_NAME_LEN. This check ensures that the name - // will not be truncated. - if (service_name.size() >= BOOTSTRAP_MAX_NAME_LEN) { - LOG(ERROR) << Traits::kName << " " << service_name << ": name too long"; - return typename Traits::Type(MACH_PORT_NULL); - } - - mach_port_t service_port; - kern_return_t kr = Traits::Call(bootstrap_port, - service_name.c_str(), - &service_port); - if (kr != BOOTSTRAP_SUCCESS) { - BOOTSTRAP_LOG(ERROR, kr) << Traits::kName << " " << service_name; - service_port = MACH_PORT_NULL; - } - - return typename Traits::Type(service_port); -} - -} // namespace - namespace crashpad { thread_t MachThreadSelf() { @@ -97,7 +45,12 @@ exception_mask_t ExcMaskAll() { // 10.9.4 xnu-2422.110.17/osfmk/mach/ipc_host.c and // xnu-2422.110.17/osfmk/mach/ipc_tt.c. -#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_9 +#if defined(OS_IOS) && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0 +// iOS 7 ≅ OS X 10.9. +#error This code was not ported to iOS versions older than 7 +#endif + +#if !defined(OS_IOS) && MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_9 const int mac_os_x_minor_version = MacOSXMinorVersion(); #endif @@ -114,7 +67,7 @@ exception_mask_t ExcMaskAll() { EXC_MASK_MACH_SYSCALL | EXC_MASK_RPC_ALERT | EXC_MASK_MACHINE; -#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_8 +#if !defined(OS_IOS) && MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_8 if (mac_os_x_minor_version < 8) { return kExcMaskAll_10_6; } @@ -124,7 +77,7 @@ exception_mask_t ExcMaskAll() { // xnu-2050.48.11/osfmk/mach/exception_types.h. constexpr exception_mask_t kExcMaskAll_10_8 = kExcMaskAll_10_6 | EXC_MASK_RESOURCE; -#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_9 +#if !defined(OS_IOS) && MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_9 if (mac_os_x_minor_version < 9) { return kExcMaskAll_10_8; } @@ -139,7 +92,12 @@ exception_mask_t ExcMaskAll() { exception_mask_t ExcMaskValid() { const exception_mask_t kExcMaskValid_10_6 = ExcMaskAll() | EXC_MASK_CRASH; -#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_11 +#if defined(OS_IOS) && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_9_0 +// iOS 9 ≅ OS X 10.11. +#error This code was not ported to iOS versions older than 9 +#endif + +#if !defined(OS_IOS) && MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_11 if (MacOSXMinorVersion() < 11) { return kExcMaskValid_10_6; } @@ -151,37 +109,4 @@ exception_mask_t ExcMaskValid() { return kExcMaskValid_10_11; } -base::mac::ScopedMachReceiveRight BootstrapCheckIn( - const std::string& service_name) { - return BootstrapCheckInOrLookUp(service_name); -} - -base::mac::ScopedMachSendRight BootstrapLookUp( - const std::string& service_name) { - base::mac::ScopedMachSendRight send( - BootstrapCheckInOrLookUp(service_name)); - - // It’s possible to race the bootstrap server when the receive right - // corresponding to the looked-up send right is destroyed immediately before - // the bootstrap_look_up() call. If the bootstrap server believes that - // |service_name| is still registered before processing the port-destroyed - // notification sent to it by the kernel, it will respond to a - // bootstrap_look_up() request with a send right that has become a dead name, - // which will be returned to the bootstrap_look_up() caller, translated into - // the caller’s IPC port name space, as the special MACH_PORT_DEAD port name. - // Check for that and return MACH_PORT_NULL in its place, as though the - // bootstrap server had fully processed the port-destroyed notification before - // responding to bootstrap_look_up(). - if (send.get() == MACH_PORT_DEAD) { - LOG(ERROR) << "bootstrap_look_up " << service_name << ": service is dead"; - send.reset(); - } - - return send; -} - -base::mac::ScopedMachSendRight SystemCrashReporterHandler() { - return BootstrapLookUp("com.apple.ReportCrash"); -} - } // namespace crashpad diff --git a/util/mach/mach_extensions.h b/util/mach/mach_extensions.h index 25e401c2..9247ce94 100644 --- a/util/mach/mach_extensions.h +++ b/util/mach/mach_extensions.h @@ -17,10 +17,6 @@ #include -#include - -#include "base/mac/scoped_mach_port.h" - namespace crashpad { //! \brief `MACH_PORT_NULL` with the correct type for a Mach port, @@ -119,43 +115,6 @@ exception_mask_t ExcMaskAll(); //! support is present. exception_mask_t ExcMaskValid(); -//! \brief Makes a `boostrap_check_in()` call to the process’ bootstrap server. -//! -//! This function is provided to make it easier to call `bootstrap_check_in()` -//! while avoiding accidental leaks of the returned receive right. -//! -//! \param[in] service_name The service name to check in. -//! -//! \return On success, a receive right to the service port. On failure, -//! `MACH_PORT_NULL` with a message logged. -base::mac::ScopedMachReceiveRight BootstrapCheckIn( - const std::string& service_name); - -//! \brief Makes a `boostrap_look_up()` call to the process’ bootstrap server. -//! -//! This function is provided to make it easier to call `bootstrap_look_up()` -//! while avoiding accidental leaks of the returned send right. -//! -//! \param[in] service_name The service name to look up. -//! -//! \return On success, a send right to the service port. On failure, -//! `MACH_PORT_NULL` with a message logged. -base::mac::ScopedMachSendRight BootstrapLookUp(const std::string& service_name); - -//! \brief Obtains the system’s default Mach exception handler for crash-type -//! exceptions. -//! -//! This is obtained by looking up `"com.apple.ReportCrash"` with the bootstrap -//! server. The service name comes from the first launch agent loaded by -//! `launchd` with a `MachServices` entry having `ExceptionServer` set. This -//! launch agent is normally loaded from -//! `/System/Library/LaunchAgents/com.apple.ReportCrash.plist`. -//! -//! \return On success, a send right to an `exception_handler_t` corresponding -//! to the system’s default crash reporter. On failure, `MACH_PORT_NULL`, -//! with a message logged. -base::mac::ScopedMachSendRight SystemCrashReporterHandler(); - } // namespace crashpad #endif // CRASHPAD_UTIL_MACH_MACH_EXTENSIONS_H_ diff --git a/util/mach/mach_extensions_test.cc b/util/mach/mach_extensions_test.cc index 1c5b368c..9fd49402 100644 --- a/util/mach/mach_extensions_test.cc +++ b/util/mach/mach_extensions_test.cc @@ -15,10 +15,10 @@ #include "util/mach/mach_extensions.h" #include "base/mac/scoped_mach_port.h" +#include "build/build_config.h" #include "gtest/gtest.h" #include "test/mac/mach_errors.h" #include "util/mac/mac_util.h" -#include "util/misc/random_string.h" namespace crashpad { namespace test { @@ -80,6 +80,11 @@ TEST(MachExtensions, ExcMaskAll) { EXPECT_FALSE(exc_mask_all & EXC_MASK_CRASH); EXPECT_FALSE(exc_mask_all & EXC_MASK_CORPSE_NOTIFY); +#if defined(OS_IOS) + // Assume at least iOS 7 (≅ OS X 10.9). + EXPECT_TRUE(exc_mask_all & EXC_MASK_RESOURCE); + EXPECT_TRUE(exc_mask_all & EXC_MASK_GUARD); +#else // OS_IOS const int mac_os_x_minor_version = MacOSXMinorVersion(); if (mac_os_x_minor_version >= 8) { EXPECT_TRUE(exc_mask_all & EXC_MASK_RESOURCE); @@ -92,6 +97,7 @@ TEST(MachExtensions, ExcMaskAll) { } else { EXPECT_FALSE(exc_mask_all & EXC_MASK_GUARD); } +#endif // OS_IOS // Bit 0 should not be set. EXPECT_FALSE(ExcMaskAll() & 1); @@ -106,6 +112,12 @@ TEST(MachExtensions, ExcMaskValid) { EXPECT_TRUE(exc_mask_valid & EXC_MASK_CRASH); +#if defined(OS_IOS) + // Assume at least iOS 9 (≅ OS X 10.11). + EXPECT_TRUE(exc_mask_valid & EXC_MASK_RESOURCE); + EXPECT_TRUE(exc_mask_valid & EXC_MASK_GUARD); + EXPECT_TRUE(exc_mask_valid & EXC_MASK_CORPSE_NOTIFY); +#else // OS_IOS const int mac_os_x_minor_version = MacOSXMinorVersion(); if (mac_os_x_minor_version >= 8) { EXPECT_TRUE(exc_mask_valid & EXC_MASK_RESOURCE); @@ -124,6 +136,7 @@ TEST(MachExtensions, ExcMaskValid) { } else { EXPECT_FALSE(exc_mask_valid & EXC_MASK_CORPSE_NOTIFY); } +#endif // OS_IOS // Bit 0 should not be set. EXPECT_FALSE(ExcMaskValid() & 1); @@ -132,48 +145,6 @@ TEST(MachExtensions, ExcMaskValid) { EXPECT_TRUE(ExcMaskValid() & ~ExcMaskAll()); } -TEST(MachExtensions, BootstrapCheckInAndLookUp) { - // This should always exist. - base::mac::ScopedMachSendRight - report_crash(BootstrapLookUp("com.apple.ReportCrash")); - EXPECT_NE(report_crash, kMachPortNull); - - std::string service_name = "org.chromium.crashpad.test.bootstrap_check_in."; - service_name.append(RandomString()); - - { - // The new service hasn’t checked in yet, so this should fail. - base::mac::ScopedMachSendRight send(BootstrapLookUp(service_name)); - EXPECT_EQ(send, kMachPortNull); - - // Check it in. - base::mac::ScopedMachReceiveRight receive(BootstrapCheckIn(service_name)); - EXPECT_NE(receive, kMachPortNull); - - // Now it should be possible to look up the new service. - send = BootstrapLookUp(service_name); - EXPECT_NE(send, kMachPortNull); - - // It shouldn’t be possible to check the service in while it’s active. - base::mac::ScopedMachReceiveRight receive_2(BootstrapCheckIn(service_name)); - EXPECT_EQ(receive_2, kMachPortNull); - } - - // The new service should be gone now. - base::mac::ScopedMachSendRight send(BootstrapLookUp(service_name)); - EXPECT_EQ(send, kMachPortNull); - - // It should be possible to check it in again. - base::mac::ScopedMachReceiveRight receive(BootstrapCheckIn(service_name)); - EXPECT_NE(receive, kMachPortNull); -} - -TEST(MachExtensions, SystemCrashReporterHandler) { - base::mac::ScopedMachSendRight - system_crash_reporter_handler(SystemCrashReporterHandler()); - EXPECT_TRUE(system_crash_reporter_handler.is_valid()); -} - } // namespace } // namespace test } // namespace crashpad diff --git a/util/mach/mach_message.cc b/util/mach/mach_message.cc index 5294c035..72d71e3e 100644 --- a/util/mach/mach_message.cc +++ b/util/mach/mach_message.cc @@ -15,7 +15,6 @@ #include "util/mach/mach_message.h" #include -#include #include @@ -24,6 +23,10 @@ #include "util/misc/clock.h" #include "util/misc/implicit_cast.h" +#if !defined(OS_IOS) +#include +#endif // !OS_IOS + namespace crashpad { namespace { @@ -217,38 +220,6 @@ const mach_msg_trailer_t* MachMessageTrailerFromHeader( return reinterpret_cast(trailer_address); } -pid_t AuditPIDFromMachMessageTrailer(const mach_msg_trailer_t* trailer) { - if (trailer->msgh_trailer_type != MACH_MSG_TRAILER_FORMAT_0) { - LOG(ERROR) << "unexpected msgh_trailer_type " << trailer->msgh_trailer_type; - return -1; - } - if (trailer->msgh_trailer_size < - REQUESTED_TRAILER_SIZE(kMachMessageReceiveAuditTrailer)) { - LOG(ERROR) << "small msgh_trailer_size " << trailer->msgh_trailer_size; - return -1; - } - - const mach_msg_audit_trailer_t* audit_trailer = - reinterpret_cast(trailer); - -#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_8 - pid_t audit_pid; - audit_token_to_au32(audit_trailer->msgh_audit, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - &audit_pid, - nullptr, - nullptr); -#else - pid_t audit_pid = audit_token_to_pid(audit_trailer->msgh_audit); -#endif - - return audit_pid; -} - bool MachMessageDestroyReceivedPort(mach_port_t port, mach_msg_type_name_t port_right_type) { // This implements a subset of 10.10.5 @@ -282,4 +253,40 @@ bool MachMessageDestroyReceivedPort(mach_port_t port, } } +#if !defined(OS_IOS) + +pid_t AuditPIDFromMachMessageTrailer(const mach_msg_trailer_t* trailer) { + if (trailer->msgh_trailer_type != MACH_MSG_TRAILER_FORMAT_0) { + LOG(ERROR) << "unexpected msgh_trailer_type " << trailer->msgh_trailer_type; + return -1; + } + if (trailer->msgh_trailer_size < + REQUESTED_TRAILER_SIZE(kMachMessageReceiveAuditTrailer)) { + LOG(ERROR) << "small msgh_trailer_size " << trailer->msgh_trailer_size; + return -1; + } + + const mach_msg_audit_trailer_t* audit_trailer = + reinterpret_cast(trailer); + +#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_8 + pid_t audit_pid; + audit_token_to_au32(audit_trailer->msgh_audit, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + &audit_pid, + nullptr, + nullptr); +#else + pid_t audit_pid = audit_token_to_pid(audit_trailer->msgh_audit); +#endif + + return audit_pid; +} + +#endif // !OS_IOS + } // namespace crashpad diff --git a/util/mach/mach_message.h b/util/mach/mach_message.h index e0fc6f6f..fd8d3d58 100644 --- a/util/mach/mach_message.h +++ b/util/mach/mach_message.h @@ -19,6 +19,8 @@ #include #include +#include "build/build_config.h" + namespace crashpad { //! \brief A Mach message option specifying that an audit trailer should be @@ -166,6 +168,23 @@ void SetMIGReplyError(mach_msg_header_t* out_header, kern_return_t error); const mach_msg_trailer_t* MachMessageTrailerFromHeader( const mach_msg_header_t* header); +//! \brief Destroys or deallocates a Mach port received in a Mach message. +//! +//! This function disposes of port rights received in a Mach message. Receive +//! rights will be destroyed with `mach_port_mod_refs()`. Send and send-once +//! rights will be deallocated with `mach_port_deallocate()`. +//! +//! \param[in] port The port to destroy or deallocate. +//! \param[in] port_right_type The right type held for \a port: +//! `MACH_MSG_TYPE_PORT_RECEIVE`, `MACH_MSG_TYPE_PORT_SEND`, or +//! `MACH_MSG_TYPE_PORT_SEND_ONCE`. +//! +//! \return `true` on success, or `false` on failure with a message logged. +bool MachMessageDestroyReceivedPort(mach_port_t port, + mach_msg_type_name_t port_right_type); + +#if !defined(OS_IOS) || DOXYGEN + //! \brief Returns the process ID of a Mach message’s sender from its audit //! trailer. //! @@ -182,20 +201,7 @@ const mach_msg_trailer_t* MachMessageTrailerFromHeader( //! audit information. pid_t AuditPIDFromMachMessageTrailer(const mach_msg_trailer_t* trailer); -//! \brief Destroys or deallocates a Mach port received in a Mach message. -//! -//! This function disposes of port rights received in a Mach message. Receive -//! rights will be destroyed with `mach_port_mod_refs()`. Send and send-once -//! rights will be deallocated with `mach_port_deallocate()`. -//! -//! \param[in] port The port to destroy or deallocate. -//! \param[in] port_right_type The right type held for \a port: -//! `MACH_MSG_TYPE_PORT_RECEIVE`, `MACH_MSG_TYPE_PORT_SEND`, or -//! `MACH_MSG_TYPE_PORT_SEND_ONCE`. -//! -//! \return `true` on success, or `false` on failure with a message logged. -bool MachMessageDestroyReceivedPort(mach_port_t port, - mach_msg_type_name_t port_right_type); +#endif // !OS_IOS } // namespace crashpad diff --git a/util/mach/mach_message_server_test.cc b/util/mach/mach_message_server_test.cc index 16ba8f20..e3a3f94b 100644 --- a/util/mach/mach_message_server_test.cc +++ b/util/mach/mach_message_server_test.cc @@ -23,6 +23,7 @@ #include "base/mac/scoped_mach_port.h" #include "base/macros.h" +#include "base/stl_util.h" #include "gtest/gtest.h" #include "test/mac/mach_errors.h" #include "test/mac/mach_multiprocess.h" @@ -281,7 +282,7 @@ class TestMachMessageServer : public MachMessageServer::Interface, std::set MachMessageServerRequestIDs() override { static constexpr mach_msg_id_t request_ids[] = {kRequestMessageID}; return std::set(&request_ids[0], - &request_ids[arraysize(request_ids)]); + &request_ids[base::size(request_ids)]); } mach_msg_size_t MachMessageServerRequestSize() override { diff --git a/util/mach/mach_message_test.cc b/util/mach/mach_message_test.cc index d88ca855..729e3a34 100644 --- a/util/mach/mach_message_test.cc +++ b/util/mach/mach_message_test.cc @@ -17,6 +17,7 @@ #include #include "base/mac/scoped_mach_port.h" +#include "base/macros.h" #include "gtest/gtest.h" #include "test/mac/mach_errors.h" #include "util/mach/mach_extensions.h" @@ -108,46 +109,6 @@ TEST(MachMessage, MachMessageTrailerFromHeader) { EXPECT_EQ(MachMessageTrailerFromHeader(&test.receive), &test.receive.trailer); } -TEST(MachMessage, AuditPIDFromMachMessageTrailer) { - base::mac::ScopedMachReceiveRight port(NewMachPort(MACH_PORT_RIGHT_RECEIVE)); - ASSERT_NE(port, kMachPortNull); - - mach_msg_empty_send_t send = {}; - send.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND_ONCE, 0); - send.header.msgh_size = sizeof(send); - send.header.msgh_remote_port = port.get(); - mach_msg_return_t mr = - MachMessageWithDeadline(&send.header, - MACH_SEND_MSG, - 0, - MACH_PORT_NULL, - kMachMessageDeadlineNonblocking, - MACH_PORT_NULL, - false); - ASSERT_EQ(mr, MACH_MSG_SUCCESS) - << MachErrorMessage(mr, "MachMessageWithDeadline send"); - - struct EmptyReceiveMessageWithAuditTrailer : public mach_msg_empty_send_t { - union { - mach_msg_trailer_t trailer; - mach_msg_audit_trailer_t audit_trailer; - }; - }; - - EmptyReceiveMessageWithAuditTrailer receive; - mr = MachMessageWithDeadline(&receive.header, - MACH_RCV_MSG | kMachMessageReceiveAuditTrailer, - sizeof(receive), - port.get(), - kMachMessageDeadlineNonblocking, - MACH_PORT_NULL, - false); - ASSERT_EQ(mr, MACH_MSG_SUCCESS) - << MachErrorMessage(mr, "MachMessageWithDeadline receive"); - - EXPECT_EQ(AuditPIDFromMachMessageTrailer(&receive.trailer), getpid()); -} - TEST(MachMessage, MachMessageDestroyReceivedPort) { mach_port_t port = NewMachPort(MACH_PORT_RIGHT_RECEIVE); ASSERT_NE(port, kMachPortNull); @@ -197,6 +158,50 @@ TEST(MachMessage, MachMessageDestroyReceivedPort) { EXPECT_TRUE(MachMessageDestroyReceivedPort(port, MACH_MSG_TYPE_PORT_SEND)); } +#if !defined(OS_IOS) + +TEST(MachMessage, AuditPIDFromMachMessageTrailer) { + base::mac::ScopedMachReceiveRight port(NewMachPort(MACH_PORT_RIGHT_RECEIVE)); + ASSERT_NE(port, kMachPortNull); + + mach_msg_empty_send_t send = {}; + send.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND_ONCE, 0); + send.header.msgh_size = sizeof(send); + send.header.msgh_remote_port = port.get(); + mach_msg_return_t mr = + MachMessageWithDeadline(&send.header, + MACH_SEND_MSG, + 0, + MACH_PORT_NULL, + kMachMessageDeadlineNonblocking, + MACH_PORT_NULL, + false); + ASSERT_EQ(mr, MACH_MSG_SUCCESS) + << MachErrorMessage(mr, "MachMessageWithDeadline send"); + + struct EmptyReceiveMessageWithAuditTrailer : public mach_msg_empty_send_t { + union { + mach_msg_trailer_t trailer; + mach_msg_audit_trailer_t audit_trailer; + }; + }; + + EmptyReceiveMessageWithAuditTrailer receive; + mr = MachMessageWithDeadline(&receive.header, + MACH_RCV_MSG | kMachMessageReceiveAuditTrailer, + sizeof(receive), + port.get(), + kMachMessageDeadlineNonblocking, + MACH_PORT_NULL, + false); + ASSERT_EQ(mr, MACH_MSG_SUCCESS) + << MachErrorMessage(mr, "MachMessageWithDeadline receive"); + + EXPECT_EQ(AuditPIDFromMachMessageTrailer(&receive.trailer), getpid()); +} + +#endif // !OS_IOS + } // namespace } // namespace test } // namespace crashpad diff --git a/util/mach/mig.py b/util/mach/mig.py index 9833c8c5..fa35e006 100755 --- a/util/mach/mig.py +++ b/util/mach/mig.py @@ -1,7 +1,6 @@ #!/usr/bin/env python -# coding: utf-8 -# Copyright 2014 The Crashpad Authors. All rights reserved. +# Copyright 2019 The Crashpad Authors. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,143 +14,22 @@ # See the License for the specific language governing permissions and # limitations under the License. -import argparse -import os -import re -import subprocess import sys -def FixUserImplementation(implementation): - """Rewrites a MIG-generated user implementation (.c) file. +import mig_fix +import mig_gen - Rewrites the file at |implementation| by adding “__attribute__((unused))” to - the definition of any structure typedefed as “__Reply” by searching for the - pattern unique to those structure definitions. These structures are in fact - unused in the user implementation file, and this will trigger a - -Wunused-local-typedefs warning in gcc unless removed or marked with the - “unused” attribute. - """ - - file = open(implementation, 'r+') - contents = file.read() - - pattern = re.compile('^(\t} __Reply);$', re.MULTILINE) - contents = pattern.sub(r'\1 __attribute__((unused));', contents) - - file.seek(0) - file.truncate() - file.write(contents) - file.close() - -def FixServerImplementation(implementation): - """Rewrites a MIG-generated server implementation (.c) file. - - Rewrites the file at |implementation| by replacing “mig_internal” with - “mig_external” on functions that begin with “__MIG_check__”. This makes these - functions available to other callers outside this file from a linkage - perspective. It then returns, as a list of lines, declarations that can be - added to a header file, so that other files that include that header file will - have access to these declarations from a compilation perspective. - """ - - file = open(implementation, 'r+') - contents = file.read() - - # Find interesting declarations. - declaration_pattern = \ - re.compile('^mig_internal (kern_return_t __MIG_check__.*)$', - re.MULTILINE) - declarations = declaration_pattern.findall(contents) - - # Remove “__attribute__((__unused__))” from the declarations, and call them - # “mig_external” or “extern” depending on whether “mig_external” is defined. - attribute_pattern = re.compile(r'__attribute__\(\(__unused__\)\) ') - declarations = ['#ifdef mig_external\nmig_external\n#else\nextern\n#endif\n' + - attribute_pattern.sub('', x) + - ';\n' for x in declarations] - - # Rewrite the declarations in this file as “mig_external”. - contents = declaration_pattern.sub(r'mig_external \1', contents); - - # Crashpad never implements the mach_msg_server() MIG callouts. To avoid - # needing to provide stub implementations, set KERN_FAILURE as the RetCode - # and abort(). - routine_callout_pattern = re.compile( - r'OutP->RetCode = (([a-zA-Z0-9_]+)\(.+\));') - routine_callouts = routine_callout_pattern.findall(contents) - for routine in routine_callouts: - contents = contents.replace(routine[0], 'KERN_FAILURE; abort()') - - # Include the header for abort(). - contents = '#include \n' + contents - - file.seek(0) - file.truncate() - file.write(contents) - file.close() - return declarations - -def FixHeader(header, declarations=[]): - """Rewrites a MIG-generated header (.h) file. - - Rewrites the file at |header| by placing it inside an “extern "C"” block, so - that it declares things properly when included by a C++ compilation unit. - |declarations| can be a list of additional declarations to place inside the - “extern "C"” block after the original contents of |header|. - """ - - file = open(header, 'r+') - contents = file.read() - declarations_text = ''.join(declarations) - contents = '''\ -#ifdef __cplusplus -extern "C" { -#endif - -%s -%s -#ifdef __cplusplus -} -#endif -''' % (contents, declarations_text) - file.seek(0) - file.truncate() - file.write(contents) - file.close() def main(args): - parser = argparse.ArgumentParser() - parser.add_argument('--developer-dir', help='Path to Xcode') - parser.add_argument('--sdk', help='Path to SDK') - parser.add_argument('--include', - default=[], - action='append', - help='Additional include directory') - parser.add_argument('defs') - parser.add_argument('user_c') - parser.add_argument('server_c') - parser.add_argument('user_h') - parser.add_argument('server_h') - parsed = parser.parse_args(args) + parsed = mig_gen.parse_args(args) + + interface = mig_gen.MigInterface(parsed.user_c, parsed.server_c, + parsed.user_h, parsed.server_h) + mig_gen.generate_interface(parsed.defs, interface, parsed.include, + parsed.sdk, parsed.clang_path, parsed.mig_path, + parsed.migcom_path, parsed.arch) + mig_fix.fix_interface(interface) - command = ['mig', - '-user', parsed.user_c, - '-server', parsed.server_c, - '-header', parsed.user_h, - '-sheader', parsed.server_h, - ] - if parsed.developer_dir is not None: - os.environ['DEVELOPER_DIR'] = parsed.developer_dir - if parsed.sdk is not None: - command.extend(['-isysroot', parsed.sdk]) - for include in parsed.include: - command.extend(['-I' + include]) - command.append(parsed.defs) - subprocess.check_call(command) - FixUserImplementation(parsed.user_c) - server_declarations = FixServerImplementation(parsed.server_c) - FixHeader(parsed.user_h) - FixHeader(parsed.server_h, server_declarations) if __name__ == '__main__': - sys.exit(main(sys.argv[1:])) + sys.exit(main(sys.argv[1:])) diff --git a/util/mach/mig_fix.py b/util/mach/mig_fix.py new file mode 100755 index 00000000..037746fa --- /dev/null +++ b/util/mach/mig_fix.py @@ -0,0 +1,203 @@ +#!/usr/bin/env python +# coding: utf-8 + +# Copyright 2019 The Crashpad Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse +import os +import re +import sys + +from mig_gen import MigInterface + + +def _fix_user_implementation(implementation, fixed_implementation, header, + fixed_header): + """Rewrites a MIG-generated user implementation (.c) file. + + Rewrites the file at |implementation| by adding “__attribute__((unused))” to + the definition of any structure typedefed as “__Reply” by searching for the + pattern unique to those structure definitions. These structures are in fact + unused in the user implementation file, and this will trigger a + -Wunused-local-typedefs warning in gcc unless removed or marked with the + “unused” attribute. Also changes header references to point to the new + header filename, if changed. + + If |fixed_implementation| is None, overwrites the original; otherwise, puts + the result in the file at |fixed_implementation|. + """ + + file = open(implementation, 'r+' if fixed_implementation is None else 'r') + contents = file.read() + + pattern = re.compile('^(\t} __Reply);$', re.MULTILINE) + contents = pattern.sub(r'\1 __attribute__((unused));', contents) + + if fixed_header is not None: + contents = contents.replace( + '#include "%s"' % os.path.basename(header), + '#include "%s"' % os.path.basename(fixed_header)) + + if fixed_implementation is None: + file.seek(0) + file.truncate() + else: + file.close() + file = open(fixed_implementation, 'w') + file.write(contents) + file.close() + + +def _fix_server_implementation(implementation, fixed_implementation, header, + fixed_header): + """Rewrites a MIG-generated server implementation (.c) file. + + Rewrites the file at |implementation| by replacing “mig_internal” with + “mig_external” on functions that begin with “__MIG_check__”. This makes + these functions available to other callers outside this file from a linkage + perspective. It then returns, as a list of lines, declarations that can be + added to a header file, so that other files that include that header file + will have access to these declarations from a compilation perspective. Also + changes header references to point to the new header filename, if changed. + + If |fixed_implementation| is None or not provided, overwrites the original; + otherwise, puts the result in the file at |fixed_implementation|. + """ + + file = open(implementation, 'r+' if fixed_implementation is None else 'r') + contents = file.read() + + # Find interesting declarations. + declaration_pattern = re.compile( + '^mig_internal (kern_return_t __MIG_check__.*)$', re.MULTILINE) + declarations = declaration_pattern.findall(contents) + + # Remove “__attribute__((__unused__))” from the declarations, and call them + # “mig_external” or “extern” depending on whether “mig_external” is defined. + attribute_pattern = re.compile(r'__attribute__\(\(__unused__\)\) ') + declarations = [ + '''\ +#ifdef mig_external +mig_external +#else +extern +#endif +''' + attribute_pattern.sub('', x) + ';\n' for x in declarations + ] + + # Rewrite the declarations in this file as “mig_external”. + contents = declaration_pattern.sub(r'mig_external \1', contents) + + # Crashpad never implements the mach_msg_server() MIG callouts. To avoid + # needing to provide stub implementations, set KERN_FAILURE as the RetCode + # and abort(). + routine_callout_pattern = re.compile( + r'OutP->RetCode = (([a-zA-Z0-9_]+)\(.+\));') + routine_callouts = routine_callout_pattern.findall(contents) + for routine in routine_callouts: + contents = contents.replace(routine[0], 'KERN_FAILURE; abort()') + + # Include the header for abort(). + contents = '#include \n' + contents + + if fixed_header is not None: + contents = contents.replace( + '#include "%s"' % os.path.basename(header), + '#include "%s"' % os.path.basename(fixed_header)) + + if fixed_implementation is None: + file.seek(0) + file.truncate() + else: + file.close() + file = open(fixed_implementation, 'w') + file.write(contents) + file.close() + return declarations + + +def _fix_header(header, fixed_header, declarations=[]): + """Rewrites a MIG-generated header (.h) file. + + Rewrites the file at |header| by placing it inside an “extern "C"” block, so + that it declares things properly when included by a C++ compilation unit. + |declarations| can be a list of additional declarations to place inside the + “extern "C"” block after the original contents of |header|. + + If |fixed_header| is None or not provided, overwrites the original; + otherwise, puts the result in the file at |fixed_header|. + """ + + file = open(header, 'r+' if fixed_header is None else 'r') + contents = file.read() + declarations_text = ''.join(declarations) + contents = '''\ +#ifdef __cplusplus +extern "C" { +#endif + +%s +%s +#ifdef __cplusplus +} +#endif +''' % (contents, declarations_text) + + if fixed_header is None: + file.seek(0) + file.truncate() + else: + file.close() + file = open(fixed_header, 'w') + file.write(contents) + file.close() + + +def fix_interface(interface, fixed_interface=None): + if fixed_interface is None: + fixed_interface = MigInterface(None, None, None, None) + + _fix_user_implementation(interface.user_c, fixed_interface.user_c, + interface.user_h, fixed_interface.user_h) + server_declarations = _fix_server_implementation(interface.server_c, + fixed_interface.server_c, + interface.server_h, + fixed_interface.server_h) + _fix_header(interface.user_h, fixed_interface.user_h) + _fix_header(interface.server_h, fixed_interface.server_h, + server_declarations) + + +def main(args): + parser = argparse.ArgumentParser() + parser.add_argument('user_c') + parser.add_argument('--fixed_user_c', default=None) + parser.add_argument('server_c') + parser.add_argument('--fixed_server_c', default=None) + parser.add_argument('user_h') + parser.add_argument('--fixed_user_h', default=None) + parser.add_argument('server_h') + parser.add_argument('--fixed_server_h', default=None) + parsed = parser.parse_args(args) + + interface = MigInterface(parsed.user_c, parsed.server_c, parsed.user_h, + parsed.server_h) + fixed_interface = MigInterface(parsed.fixed_user_c, parsed.fixed_server_c, + parsed.fixed_user_h, parsed.fixed_server_h) + fix_interface(interface, fixed_interface) + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/util/mach/mig_gen.py b/util/mach/mig_gen.py new file mode 100755 index 00000000..dcbf8296 --- /dev/null +++ b/util/mach/mig_gen.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python + +# Copyright 2019 The Crashpad Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse +import collections +import os +import subprocess +import sys + +MigInterface = collections.namedtuple( + 'MigInterface', ['user_c', 'server_c', 'user_h', 'server_h']) + + +def generate_interface(defs, + interface, + includes=[], + sdk=None, + clang_path=None, + mig_path=None, + migcom_path=None, + arch=None): + if mig_path is None: + mig_path = 'mig' + + # yapf: disable + command = [ + mig_path, + '-user', interface.user_c, + '-server', interface.server_c, + '-header', interface.user_h, + '-sheader', interface.server_h, + ] + # yapf: enable + + if clang_path is not None: + os.environ['MIGCC'] = clang_path + if migcom_path is not None: + os.environ['MIGCOM'] = migcom_path + if arch is not None: + command.extend(['-arch', arch]) + if sdk is not None: + command.extend(['-isysroot', sdk]) + for include in includes: + command.extend(['-I' + include]) + command.append(defs) + subprocess.check_call(command) + + +def parse_args(args): + parser = argparse.ArgumentParser() + parser.add_argument('--clang-path', help='Path to Clang') + parser.add_argument('--mig-path', help='Path to mig') + parser.add_argument('--migcom-path', help='Path to migcom') + parser.add_argument('--arch', help='Target architecture') + parser.add_argument('--sdk', help='Path to SDK') + parser.add_argument('--include', + default=[], + action='append', + help='Additional include directory') + parser.add_argument('defs') + parser.add_argument('user_c') + parser.add_argument('server_c') + parser.add_argument('user_h') + parser.add_argument('server_h') + return parser.parse_args(args) + + +def main(args): + parsed = parse_args(args) + interface = MigInterface(parsed.user_c, parsed.server_c, parsed.user_h, + parsed.server_h) + generate_interface(parsed.defs, interface, parsed.include, parsed.sdk, + parsed.clang_path, parsed.mig_path, parsed.migcom_path, + parsed.arch) + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/util/mach/notify_server.cc b/util/mach/notify_server.cc index 711529b1..b9da7958 100644 --- a/util/mach/notify_server.cc +++ b/util/mach/notify_server.cc @@ -15,6 +15,7 @@ #include "util/mach/notify_server.h" #include "base/logging.h" +#include "base/stl_util.h" #include "util/mach/mach_message.h" #include "util/mach/notifyServer.h" @@ -227,7 +228,7 @@ std::set NotifyServer::MachMessageServerRequestIDs() { MACH_NOTIFY_DEAD_NAME, }; return std::set(&request_ids[0], - &request_ids[arraysize(request_ids)]); + &request_ids[base::size(request_ids)]); } mach_msg_size_t NotifyServer::MachMessageServerRequestSize() { diff --git a/util/mach/notify_server_test.cc b/util/mach/notify_server_test.cc index b5e152db..d334e571 100644 --- a/util/mach/notify_server_test.cc +++ b/util/mach/notify_server_test.cc @@ -31,6 +31,7 @@ namespace test { namespace { using testing::AllOf; +using testing::DoAll; using testing::Eq; using testing::Invoke; using testing::Pointee; diff --git a/util/mach/symbolic_constants_mach.cc b/util/mach/symbolic_constants_mach.cc index 98a2d203..71fe1154 100644 --- a/util/mach/symbolic_constants_mach.cc +++ b/util/mach/symbolic_constants_mach.cc @@ -17,7 +17,7 @@ #include #include -#include "base/macros.h" +#include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "util/mach/exception_behaviors.h" #include "util/mach/mach_extensions.h" @@ -45,7 +45,7 @@ constexpr const char* kExceptionNames[] = { "GUARD", "CORPSE_NOTIFY", }; -static_assert(arraysize(kExceptionNames) == EXC_TYPES_COUNT, +static_assert(base::size(kExceptionNames) == EXC_TYPES_COUNT, "kExceptionNames length"); constexpr char kExcPrefix[] = "EXC_"; @@ -170,8 +170,7 @@ std::string ThreadStateFlavorFullToShort(const base::StringPiece& flavor) { {"_STATE32", "32"}, {"_STATE64", "64"}, }; - for (size_t suffix_index = 0; - suffix_index < arraysize(kStateSuffixes); + for (size_t suffix_index = 0; suffix_index < base::size(kStateSuffixes); ++suffix_index) { const char* suffix = kStateSuffixes[suffix_index].orig; size_t suffix_len = strlen(suffix); @@ -195,7 +194,7 @@ namespace crashpad { std::string ExceptionToString(exception_type_t exception, SymbolicConstantToStringOptions options) { const char* exception_name = - implicit_cast(exception) < arraysize(kExceptionNames) + implicit_cast(exception) < base::size(kExceptionNames) ? kExceptionNames[exception] : nullptr; if (!exception_name) { @@ -221,7 +220,7 @@ bool StringToException(const base::StringPiece& string, base::StringPiece short_string = can_match_full ? string.substr(strlen(kExcPrefix)) : string; for (exception_type_t index = 0; - index < implicit_cast(arraysize(kExceptionNames)); + index < implicit_cast(base::size(kExceptionNames)); ++index) { const char* exception_name = kExceptionNames[index]; if (!exception_name) { @@ -251,8 +250,7 @@ std::string ExceptionMaskToString(exception_mask_t exception_mask, exception_mask_t local_exception_mask = exception_mask; std::string mask_string; bool has_forbidden_or = false; - for (size_t exception = 0; - exception < arraysize(kExceptionNames); + for (size_t exception = 0; exception < base::size(kExceptionNames); ++exception) { const char* exception_name = kExceptionNames[exception]; exception_mask_t exception_mask_value = 1 << exception; @@ -326,7 +324,7 @@ bool StringToExceptionMask(const base::StringPiece& string, base::StringPiece short_string = can_match_full ? string.substr(strlen(kExcMaskPrefix)) : string; for (exception_type_t index = 0; - index < implicit_cast(arraysize(kExceptionNames)); + index < implicit_cast(base::size(kExceptionNames)); ++index) { const char* exception_name = kExceptionNames[index]; if (!exception_name) { @@ -365,7 +363,7 @@ std::string ExceptionBehaviorToString(exception_behavior_t behavior, const exception_behavior_t basic_behavior = ExceptionBehaviorBasic(behavior); const char* behavior_name = - implicit_cast(basic_behavior) < arraysize(kBehaviorNames) + implicit_cast(basic_behavior) < base::size(kBehaviorNames) ? kBehaviorNames[basic_behavior] : nullptr; if (!behavior_name) { @@ -432,7 +430,8 @@ bool StringToExceptionBehavior(const base::StringPiece& string, base::StringPiece short_string = can_match_full ? sp.substr(strlen(kBehaviorPrefix)) : sp; for (exception_behavior_t index = 0; - index < implicit_cast(arraysize(kBehaviorNames)); + index < + implicit_cast(base::size(kBehaviorNames)); ++index) { const char* behavior_name = kBehaviorNames[index]; if (!behavior_name) { @@ -468,13 +467,13 @@ bool StringToExceptionBehavior(const base::StringPiece& string, std::string ThreadStateFlavorToString(thread_state_flavor_t flavor, SymbolicConstantToStringOptions options) { const char* flavor_name = - implicit_cast(flavor) < arraysize(kFlavorNames) + implicit_cast(flavor) < base::size(kFlavorNames) ? kFlavorNames[flavor] : nullptr; if (!flavor_name) { for (size_t generic_flavor_index = 0; - generic_flavor_index < arraysize(kGenericFlavorNames); + generic_flavor_index < base::size(kGenericFlavorNames); ++generic_flavor_index) { if (flavor == kGenericFlavorNames[generic_flavor_index].flavor) { flavor_name = kGenericFlavorNames[generic_flavor_index].name; @@ -501,7 +500,7 @@ bool StringToThreadStateFlavor(const base::StringPiece& string, thread_state_flavor_t* flavor) { if ((options & kAllowFullName) || (options & kAllowShortName)) { for (thread_state_flavor_t index = 0; - index < implicit_cast(arraysize(kFlavorNames)); + index < implicit_cast(base::size(kFlavorNames)); ++index) { const char* flavor_name = kFlavorNames[index]; if (!flavor_name) { @@ -521,7 +520,7 @@ bool StringToThreadStateFlavor(const base::StringPiece& string, } for (size_t generic_flavor_index = 0; - generic_flavor_index < arraysize(kGenericFlavorNames); + generic_flavor_index < base::size(kGenericFlavorNames); ++generic_flavor_index) { const char* flavor_name = kGenericFlavorNames[generic_flavor_index].name; thread_state_flavor_t flavor_number = diff --git a/util/mach/symbolic_constants_mach_test.cc b/util/mach/symbolic_constants_mach_test.cc index 3bf5abb1..e58a9c53 100644 --- a/util/mach/symbolic_constants_mach_test.cc +++ b/util/mach/symbolic_constants_mach_test.cc @@ -18,14 +18,15 @@ #include #include -#include "base/macros.h" +#include "base/stl_util.h" #include "base/strings/string_piece.h" #include "base/strings/stringprintf.h" #include "gtest/gtest.h" #include "util/mach/mach_extensions.h" #include "util/misc/implicit_cast.h" -#define NUL_TEST_DATA(string) { string, arraysize(string) - 1 } +#define NUL_TEST_DATA(string) \ + { string, base::size(string) - 1 } namespace crashpad { namespace test { @@ -159,7 +160,7 @@ void TestExceptionToString(exception_type_t value, } TEST(SymbolicConstantsMach, ExceptionToString) { - for (size_t index = 0; index < arraysize(kExceptionTestData); ++index) { + for (size_t index = 0; index < base::size(kExceptionTestData); ++index) { SCOPED_TRACE(base::StringPrintf("index %zu", index)); TestExceptionToString(kExceptionTestData[index].exception, kExceptionTestData[index].full_name, @@ -187,12 +188,11 @@ void TestStringToException(const base::StringPiece& string, } TEST(SymbolicConstantsMach, StringToException) { - for (size_t option_index = 0; - option_index < arraysize(kNormalOptions); + for (size_t option_index = 0; option_index < base::size(kNormalOptions); ++option_index) { SCOPED_TRACE(base::StringPrintf("option_index %zu", option_index)); StringToSymbolicConstantOptions options = kNormalOptions[option_index]; - for (size_t index = 0; index < arraysize(kExceptionTestData); ++index) { + for (size_t index = 0; index < base::size(kExceptionTestData); ++index) { SCOPED_TRACE(base::StringPrintf("index %zu", index)); exception_type_t exception = kExceptionTestData[index].exception; { @@ -230,7 +230,7 @@ TEST(SymbolicConstantsMach, StringToException) { "", }; - for (size_t index = 0; index < arraysize(kNegativeTestData); ++index) { + for (size_t index = 0; index < base::size(kNegativeTestData); ++index) { SCOPED_TRACE(base::StringPrintf("index %zu", index)); TestStringToException(kNegativeTestData[index], options, false, 0); } @@ -251,7 +251,7 @@ TEST(SymbolicConstantsMach, StringToException) { NUL_TEST_DATA("1\0002"), }; - for (size_t index = 0; index < arraysize(kNULTestData); ++index) { + for (size_t index = 0; index < base::size(kNULTestData); ++index) { SCOPED_TRACE(base::StringPrintf("index %zu", index)); base::StringPiece string(kNULTestData[index].string, kNULTestData[index].length); @@ -334,7 +334,7 @@ void TestExceptionMaskToString(exception_mask_t value, } TEST(SymbolicConstantsMach, ExceptionMaskToString) { - for (size_t index = 0; index < arraysize(kExceptionMaskTestData); ++index) { + for (size_t index = 0; index < base::size(kExceptionMaskTestData); ++index) { SCOPED_TRACE(base::StringPrintf("index %zu", index)); TestExceptionMaskToString(kExceptionMaskTestData[index].exception_mask, kExceptionMaskTestData[index].full_name, @@ -389,12 +389,12 @@ TEST(SymbolicConstantsMach, StringToExceptionMask) { kAllowFullName | kAllowShortName | kAllowNumber | kAllowOr, }; - for (size_t option_index = 0; - option_index < arraysize(kOptions); + for (size_t option_index = 0; option_index < base::size(kOptions); ++option_index) { SCOPED_TRACE(base::StringPrintf("option_index %zu", option_index)); StringToSymbolicConstantOptions options = kOptions[option_index]; - for (size_t index = 0; index < arraysize(kExceptionMaskTestData); ++index) { + for (size_t index = 0; index < base::size(kExceptionMaskTestData); + ++index) { SCOPED_TRACE(base::StringPrintf("index %zu", index)); exception_mask_t exception_mask = kExceptionMaskTestData[index].exception_mask; @@ -445,7 +445,7 @@ TEST(SymbolicConstantsMach, StringToExceptionMask) { "", }; - for (size_t index = 0; index < arraysize(kNegativeTestData); ++index) { + for (size_t index = 0; index < base::size(kNegativeTestData); ++index) { SCOPED_TRACE(base::StringPrintf("index %zu", index)); TestStringToExceptionMask(kNegativeTestData[index], options, false, 0); } @@ -471,7 +471,7 @@ TEST(SymbolicConstantsMach, StringToExceptionMask) { NUL_TEST_DATA("ARITHMETIC|\0EMULATION"), }; - for (size_t index = 0; index < arraysize(kNULTestData); ++index) { + for (size_t index = 0; index < base::size(kNULTestData); ++index) { SCOPED_TRACE(base::StringPrintf("index %zu", index)); base::StringPiece string(kNULTestData[index].string, kNULTestData[index].length); @@ -506,7 +506,7 @@ TEST(SymbolicConstantsMach, StringToExceptionMask) { EXC_MASK_SYSCALL | 0x100}, }; - for (size_t index = 0; index < arraysize(kNonCanonicalTestData); ++index) { + for (size_t index = 0; index < base::size(kNonCanonicalTestData); ++index) { SCOPED_TRACE(base::StringPrintf("index %zu", index)); TestStringToExceptionMask(kNonCanonicalTestData[index].string, kNonCanonicalTestData[index].options, @@ -577,8 +577,7 @@ void TestExceptionBehaviorToString(exception_behavior_t value, } TEST(SymbolicConstantsMach, ExceptionBehaviorToString) { - for (size_t index = 0; - index < arraysize(kExceptionBehaviorTestData); + for (size_t index = 0; index < base::size(kExceptionBehaviorTestData); ++index) { SCOPED_TRACE(base::StringPrintf("index %zu", index)); TestExceptionBehaviorToString(kExceptionBehaviorTestData[index].behavior, @@ -608,13 +607,11 @@ void TestStringToExceptionBehavior(const base::StringPiece& string, } TEST(SymbolicConstantsMach, StringToExceptionBehavior) { - for (size_t option_index = 0; - option_index < arraysize(kNormalOptions); + for (size_t option_index = 0; option_index < base::size(kNormalOptions); ++option_index) { SCOPED_TRACE(base::StringPrintf("option_index %zu", option_index)); StringToSymbolicConstantOptions options = kNormalOptions[option_index]; - for (size_t index = 0; - index < arraysize(kExceptionBehaviorTestData); + for (size_t index = 0; index < base::size(kExceptionBehaviorTestData); ++index) { SCOPED_TRACE(base::StringPrintf("index %zu", index)); exception_behavior_t behavior = @@ -660,7 +657,7 @@ TEST(SymbolicConstantsMach, StringToExceptionBehavior) { "", }; - for (size_t index = 0; index < arraysize(kNegativeTestData); ++index) { + for (size_t index = 0; index < base::size(kNegativeTestData); ++index) { SCOPED_TRACE(base::StringPrintf("index %zu", index)); TestStringToExceptionBehavior( kNegativeTestData[index], options, false, 0); @@ -686,7 +683,7 @@ TEST(SymbolicConstantsMach, StringToExceptionBehavior) { NUL_TEST_DATA("STATE_IDENTITY|\0MACH"), }; - for (size_t index = 0; index < arraysize(kNULTestData); ++index) { + for (size_t index = 0; index < base::size(kNULTestData); ++index) { SCOPED_TRACE(base::StringPrintf("index %zu", index)); base::StringPiece string(kNULTestData[index].string, kNULTestData[index].length); @@ -723,7 +720,7 @@ TEST(SymbolicConstantsMach, StringToExceptionBehavior) { implicit_cast(MACH_EXCEPTION_CODES | 0x2)}, }; - for (size_t index = 0; index < arraysize(kNonCanonicalTestData); ++index) { + for (size_t index = 0; index < base::size(kNonCanonicalTestData); ++index) { SCOPED_TRACE(base::StringPrintf("index %zu", index)); TestStringToExceptionBehavior(kNonCanonicalTestData[index].string, kNonCanonicalTestData[index].options, @@ -840,8 +837,7 @@ void TestThreadStateFlavorToString(exception_type_t value, } TEST(SymbolicConstantsMach, ThreadStateFlavorToString) { - for (size_t index = 0; - index < arraysize(kThreadStateFlavorTestData); + for (size_t index = 0; index < base::size(kThreadStateFlavorTestData); ++index) { SCOPED_TRACE(base::StringPrintf("index %zu", index)); TestThreadStateFlavorToString(kThreadStateFlavorTestData[index].flavor, @@ -883,13 +879,11 @@ void TestStringToThreadStateFlavor(const base::StringPiece& string, } TEST(SymbolicConstantsMach, StringToThreadStateFlavor) { - for (size_t option_index = 0; - option_index < arraysize(kNormalOptions); + for (size_t option_index = 0; option_index < base::size(kNormalOptions); ++option_index) { SCOPED_TRACE(base::StringPrintf("option_index %zu", option_index)); StringToSymbolicConstantOptions options = kNormalOptions[option_index]; - for (size_t index = 0; - index < arraysize(kThreadStateFlavorTestData); + for (size_t index = 0; index < base::size(kThreadStateFlavorTestData); ++index) { SCOPED_TRACE(base::StringPrintf("index %zu", index)); thread_state_flavor_t flavor = kThreadStateFlavorTestData[index].flavor; @@ -959,7 +953,7 @@ TEST(SymbolicConstantsMach, StringToThreadStateFlavor) { #endif }; - for (size_t index = 0; index < arraysize(kNegativeTestData); ++index) { + for (size_t index = 0; index < base::size(kNegativeTestData); ++index) { SCOPED_TRACE(base::StringPrintf("index %zu", index)); TestStringToThreadStateFlavor( kNegativeTestData[index], options, false, 0); @@ -1025,7 +1019,7 @@ TEST(SymbolicConstantsMach, StringToThreadStateFlavor) { #endif }; - for (size_t index = 0; index < arraysize(kNULTestData); ++index) { + for (size_t index = 0; index < base::size(kNULTestData); ++index) { SCOPED_TRACE(base::StringPrintf("index %zu", index)); base::StringPiece string(kNULTestData[index].string, kNULTestData[index].length); diff --git a/util/misc/arraysize.h b/util/misc/arraysize.h new file mode 100644 index 00000000..93a63882 --- /dev/null +++ b/util/misc/arraysize.h @@ -0,0 +1,43 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_MISC_ARRAYSIZE_H_ +#define CRASHPAD_UTIL_MISC_ARRAYSIZE_H_ + +#include // For size_t. + +#include + +//! \file + +namespace crashpad { +namespace internal { + +//! \brief A helper to implement ArraySize. +template +constexpr size_t ArraySizeHelper() noexcept { + return std::extent::type>::value; +} + +} // namespace internal +} // namespace crashpad + +//! \brief A way of computing an array’s size. +//! +//! Use this only where `base::size()` or `std::size()` won’t work, such as in +//! constant expressions (including `static_assert` expressions) that consider +//! the sizes of non-static data members. +#define ArraySize(array) crashpad::internal::ArraySizeHelper() + +#endif // CRASHPAD_UTIL_MISC_ARRAYSIZE_H_ diff --git a/util/misc/arraysize_unsafe_test.cc b/util/misc/arraysize_test.cc similarity index 68% rename from util/misc/arraysize_unsafe_test.cc rename to util/misc/arraysize_test.cc index a6660aee..ad8da097 100644 --- a/util/misc/arraysize_unsafe_test.cc +++ b/util/misc/arraysize_test.cc @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "util/misc/arraysize_unsafe.h" +#include "util/misc/arraysize.h" #include "base/compiler_specific.h" #include "gtest/gtest.h" @@ -21,37 +21,37 @@ namespace crashpad { namespace test { namespace { -TEST(ArraySizeUnsafe, ArraySizeUnsafe) { +TEST(ArraySize, ArraySize) { char c1[1]; - static_assert(ARRAYSIZE_UNSAFE(c1) == 1, "c1"); + static_assert(ArraySize(c1) == 1, "c1"); ALLOW_UNUSED_LOCAL(c1); char c2[2]; - static_assert(ARRAYSIZE_UNSAFE(c2) == 2, "c2"); + static_assert(ArraySize(c2) == 2, "c2"); ALLOW_UNUSED_LOCAL(c2); char c4[4]; - static_assert(ARRAYSIZE_UNSAFE(c4) == 4, "c4"); + static_assert(ArraySize(c4) == 4, "c4"); ALLOW_UNUSED_LOCAL(c4); int i1[1]; - static_assert(ARRAYSIZE_UNSAFE(i1) == 1, "i1"); + static_assert(ArraySize(i1) == 1, "i1"); ALLOW_UNUSED_LOCAL(i1); int i2[2]; - static_assert(ARRAYSIZE_UNSAFE(i2) == 2, "i2"); + static_assert(ArraySize(i2) == 2, "i2"); ALLOW_UNUSED_LOCAL(i2); int i4[4]; - static_assert(ARRAYSIZE_UNSAFE(i4) == 4, "i4"); + static_assert(ArraySize(i4) == 4, "i4"); ALLOW_UNUSED_LOCAL(i4); long l8[8]; - static_assert(ARRAYSIZE_UNSAFE(l8) == 8, "l8"); + static_assert(ArraySize(l8) == 8, "l8"); ALLOW_UNUSED_LOCAL(l8); int l9[9]; - static_assert(ARRAYSIZE_UNSAFE(l9) == 9, "l9"); + static_assert(ArraySize(l9) == 9, "l9"); ALLOW_UNUSED_LOCAL(l9); struct S { @@ -62,11 +62,11 @@ TEST(ArraySizeUnsafe, ArraySizeUnsafe) { }; S s1[1]; - static_assert(ARRAYSIZE_UNSAFE(s1) == 1, "s1"); + static_assert(ArraySize(s1) == 1, "s1"); ALLOW_UNUSED_LOCAL(s1); S s10[10]; - static_assert(ARRAYSIZE_UNSAFE(s10) == 10, "s10"); + static_assert(ArraySize(s10) == 10, "s10"); ALLOW_UNUSED_LOCAL(s10); } diff --git a/util/misc/capture_context.h b/util/misc/capture_context.h index 541589df..3ff71184 100644 --- a/util/misc/capture_context.h +++ b/util/misc/capture_context.h @@ -32,6 +32,8 @@ namespace crashpad { #if defined(OS_MACOSX) #if defined(ARCH_CPU_X86_FAMILY) using NativeCPUContext = x86_thread_state; +#elif defined(ARCH_CPU_ARM64) +using NativeCPUContext = arm_unified_thread_state; #endif #elif defined(OS_WIN) using NativeCPUContext = CONTEXT; diff --git a/util/misc/capture_context_fuchsia.S b/util/misc/capture_context_fuchsia.S index 8e5c0cbe..0ebc7f7f 100644 --- a/util/misc/capture_context_fuchsia.S +++ b/util/misc/capture_context_fuchsia.S @@ -116,7 +116,7 @@ CAPTURECONTEXT_SYMBOL: movq 0x90(%rdi), %rax movq 0x28(%rdi), %r8 - // TODO(scottmg): save floating-point registers. + // TODO(https://crashpad.chromium.org/bug/300): save floating-point registers. popfq @@ -159,14 +159,14 @@ CAPTURECONTEXT_SYMBOL: // The link register holds the return address for this function. str LR, [x0, #0x1b8] // context->uc_mcontext.pc - // NZCV, pstate, and CPSR are synonyms. + // pstate should hold SPSR but NZCV are the only bits we know about. mrs x1, NZCV str x1, [x0, #0x1c0] // context->uc_mcontext.pstate // Restore x1 from the saved context. ldr x1, [x0, #0xc0] - // TODO(scottmg): save floating-point registers. + // TODO(https://crashpad.chromium.org/bug/300): save floating-point registers. ret diff --git a/util/misc/capture_context_linux.S b/util/misc/capture_context_linux.S index 42999a90..52215ee5 100644 --- a/util/misc/capture_context_linux.S +++ b/util/misc/capture_context_linux.S @@ -28,7 +28,11 @@ .globl CAPTURECONTEXT_SYMBOL2 #if defined(__i386__) || defined(__x86_64__) .balign 16, 0x90 -#elif defined(__arm__) || defined(__aarch64__) || defined(__mips__) +#elif defined(__arm__) || defined(__aarch64__) + .balign 4, 0x0 + .type CAPTURECONTEXT_SYMBOL, %function + .type CAPTURECONTEXT_SYMBOL2, %function +#elif defined(__mips__) .balign 4, 0x0 #endif @@ -257,9 +261,7 @@ CAPTURECONTEXT_SYMBOL2: // Restore r0. sub r0, r0, #0x24 - // Save named general purpose registers. - str FP, [r0, #0x4c] // context->uc_mcontext.fp - str IP, [r0, #0x50] // context->uc_mcontext.ip + // Save SP/r13. str SP, [r0, #0x54] // context->uc_mcontext.sp // The original LR can't be recovered. @@ -284,7 +286,7 @@ CAPTURECONTEXT_SYMBOL2: // Restore r1. ldr r1, [r0, #0x24] - // TODO(jperaza): save floating-point registers. + // TODO(https://crashpad.chromium.org/bug/300): save floating-point registers. mov PC, LR @@ -312,23 +314,23 @@ CAPTURECONTEXT_SYMBOL2: stp x28, x29, [x0, #0x198] // The original LR can't be recovered. - str LR, [x0, #0x1a8] + str x30, [x0, #0x1a8] // Use x1 as a scratch register. mov x1, SP str x1, [x0, #0x1b0] // context->uc_mcontext.sp // The link register holds the return address for this function. - str LR, [x0, #0x1b8] // context->uc_mcontext.pc + str x30, [x0, #0x1b8] // context->uc_mcontext.pc - // NZCV, pstate, and CPSR are synonyms. + // pstate should hold SPSR but NZCV are the only bits we know about. mrs x1, NZCV str x1, [x0, #0x1c0] // context->uc_mcontext.pstate // Restore x1 from the saved context. ldr x1, [x0, #0xc0] - // TODO(jperaza): save floating-point registers. + // TODO(https://crashpad.chromium.org/bug/300): save floating-point registers. ret #elif defined(__mips__) diff --git a/util/misc/capture_context_test.cc b/util/misc/capture_context_test.cc index 7f3890f1..cf23c2de 100644 --- a/util/misc/capture_context_test.cc +++ b/util/misc/capture_context_test.cc @@ -18,9 +18,11 @@ #include +#include "build/build_config.h" #include "gtest/gtest.h" #include "util/misc/address_sanitizer.h" #include "util/misc/capture_context_test_util.h" +#include "util/misc/memory_sanitizer.h" namespace crashpad { namespace test { @@ -33,7 +35,12 @@ namespace { // find an approximately valid stack pointer by comparing locals to the // captured one, disable safe-stack for this function. __attribute__((no_sanitize("safe-stack"))) -#endif +#endif // defined(OS_FUCHSIA) + +#if defined(MEMORY_SANITIZER) +// CaptureContext() calls inline assembly and is incompatible with MSan. +__attribute__((no_sanitize("memory"))) +#endif // defined(MEMORY_SANITIZER) void TestCaptureContext() { NativeCPUContext context_1; @@ -49,16 +56,17 @@ void TestCaptureContext() { // reference program counter. uintptr_t pc = ProgramCounterFromContext(context_1); -#if !defined(ADDRESS_SANITIZER) && !defined(ARCH_CPU_MIPS_FAMILY) - // AddressSanitizer can cause enough code bloat that the “nearby” check would +#if !defined(ADDRESS_SANITIZER) && !defined(ARCH_CPU_MIPS_FAMILY) && \ + !defined(MEMORY_SANITIZER) + // Sanitizers can cause enough code bloat that the “nearby” check would // likely fail. const uintptr_t kReferencePC = reinterpret_cast(TestCaptureContext); EXPECT_PRED2([](uintptr_t actual, - uintptr_t reference) { return actual - reference < 64u; }, + uintptr_t reference) { return actual - reference < 128u; }, pc, kReferencePC); -#endif // !defined(ADDRESS_SANITIZER) +#endif const uintptr_t sp = StackPointerFromContext(context_1); @@ -67,9 +75,9 @@ void TestCaptureContext() { // value can be the lowest value possible. NativeCPUContext context_2; -// AddressSanitizer on Linux causes stack variables to be stored separately from -// the call stack. -#if !defined(ADDRESS_SANITIZER) || (!defined(OS_LINUX) && !defined(OS_ANDROID)) + // AddressSanitizer with use-after-return detection causes stack variables to + // be allocated on the heap. +#if !defined(ADDRESS_SANITIZER) // The stack pointer reference value is the lowest address of a local variable // in this function. The captured program counter will be slightly less than // or equal to the reference stack pointer. @@ -82,7 +90,7 @@ void TestCaptureContext() { uintptr_t reference) { return reference - actual < 768u; }, sp, kReferenceSP); -#endif // !ADDRESS_SANITIZER || (!OS_LINUX && !OS_ANDROID) +#endif // !defined(ADDRESS_SANITIZER) // Capture the context again, expecting that the stack pointer stays the same // and the program counter increases. Strictly speaking, there’s no guarantee diff --git a/util/misc/capture_context_test_util_win.cc b/util/misc/capture_context_test_util_win.cc index 239beacd..16d81b72 100644 --- a/util/misc/capture_context_test_util_win.cc +++ b/util/misc/capture_context_test_util_win.cc @@ -13,8 +13,9 @@ // limitations under the License. #include "util/misc/capture_context_test_util.h" +#include "util/win/context_wrappers.h" -#include "base/macros.h" +#include "base/stl_util.h" #include "gtest/gtest.h" namespace crashpad { @@ -58,7 +59,7 @@ void SanityCheckContext(const NativeCPUContext& context) { #if defined(ARCH_CPU_X86) // fxsave doesn’t write these bytes. - for (size_t i = 464; i < arraysize(context.ExtendedRegisters); ++i) { + for (size_t i = 464; i < base::size(context.ExtendedRegisters); ++i) { SCOPED_TRACE(i); EXPECT_EQ(context.ExtendedRegisters[i], 0); } @@ -68,7 +69,7 @@ void SanityCheckContext(const NativeCPUContext& context) { EXPECT_EQ(context.FltSave.MxCsr, context.MxCsr); // fxsave doesn’t write these bytes. - for (size_t i = 0; i < arraysize(context.FltSave.Reserved4); ++i) { + for (size_t i = 0; i < base::size(context.FltSave.Reserved4); ++i) { SCOPED_TRACE(i); EXPECT_EQ(context.FltSave.Reserved4[i], 0); } @@ -80,7 +81,7 @@ void SanityCheckContext(const NativeCPUContext& context) { EXPECT_EQ(context.P4Home, 0u); EXPECT_EQ(context.P5Home, 0u); EXPECT_EQ(context.P6Home, 0u); - for (size_t i = 0; i < arraysize(context.VectorRegister); ++i) { + for (size_t i = 0; i < base::size(context.VectorRegister); ++i) { SCOPED_TRACE(i); EXPECT_EQ(context.VectorRegister[i].Low, 0u); EXPECT_EQ(context.VectorRegister[i].High, 0u); @@ -95,11 +96,7 @@ void SanityCheckContext(const NativeCPUContext& context) { } uintptr_t ProgramCounterFromContext(const NativeCPUContext& context) { -#if defined(ARCH_CPU_X86) - return context.Eip; -#elif defined(ARCH_CPU_X86_64) - return context.Rip; -#endif + return reinterpret_cast(ProgramCounterFromCONTEXT(&context)); } uintptr_t StackPointerFromContext(const NativeCPUContext& context) { @@ -107,6 +104,8 @@ uintptr_t StackPointerFromContext(const NativeCPUContext& context) { return context.Esp; #elif defined(ARCH_CPU_X86_64) return context.Rsp; +#elif defined(ARCH_CPU_ARM64) + return context.Sp; #endif } diff --git a/util/misc/capture_context_win_arm64.asm b/util/misc/capture_context_win_arm64.asm new file mode 100644 index 00000000..5630698f --- /dev/null +++ b/util/misc/capture_context_win_arm64.asm @@ -0,0 +1,64 @@ +; Copyright 2019 The Crashpad Authors. All rights reserved. +; +; Licensed under the Apache License, Version 2.0 (the "License"); +; you may not use this file except in compliance with the License. +; You may obtain a copy of the License at +; +; http://www.apache.org/licenses/LICENSE-2.0 +; +; Unless required by applicable law or agreed to in writing, software +; distributed under the License is distributed on an "AS IS" BASIS, +; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +; See the License for the specific language governing permissions and +; limitations under the License. + + EXPORT |?CaptureContext@crashpad@@YAXPEAU_CONTEXT@@@Z| + AREA |.text|, CODE +|?CaptureContext@crashpad@@YAXPEAU_CONTEXT@@@Z| PROC + ; Save general purpose registers in context.regs[i]. + ; The original x0 can't be recovered. + stp x0, x1, [x0, #0x008] + stp x2, x3, [x0, #0x018] + stp x4, x5, [x0, #0x028] + stp x6, x7, [x0, #0x038] + stp x8, x9, [x0, #0x048] + stp x10, x11, [x0, #0x058] + stp x12, x13, [x0, #0x068] + stp x14, x15, [x0, #0x078] + stp x16, x17, [x0, #0x088] + stp x18, x19, [x0, #0x098] + stp x20, x21, [x0, #0x0a8] + stp x22, x23, [x0, #0x0b8] + stp x24, x25, [x0, #0x0c8] + stp x26, x27, [x0, #0x0d8] + stp x28, x29, [x0, #0x0e8] + + ; The original LR can't be recovered. + str LR, [x0, #0x0f8] + + ; Use x1 as a scratch register. + mov x1, SP + str x1, [x0, #0x100] ; context.sp + + ; The link register holds the return address for this function. + str LR, [x0, #0x108] ; context.pc + + ; pstate should hold SPSR but NZCV are the only bits we know about. + mrs x1, NZCV + + ; Enable Control flags, such as CONTEXT_ARM64, CONTEXT_CONTROL, + ; CONTEXT_INTEGER + ldr w1, =0x00400003 + + ; Set ControlFlags /0x000/ and pstate /0x004/ at the same time. + str x1, [x0, #0x000] + + ; Restore x1 from the saved context. + ldr x1, [x0, #0x010] + + ; TODO(https://crashpad.chromium.org/bug/300): save floating-point registers + + ret + ENDP + + END diff --git a/util/misc/capture_context_win_arm64.obj b/util/misc/capture_context_win_arm64.obj new file mode 100644 index 00000000..11c76a1a Binary files /dev/null and b/util/misc/capture_context_win_arm64.obj differ diff --git a/util/misc/clock_test.cc b/util/misc/clock_test.cc index 95330eb2..6bfb87b4 100644 --- a/util/misc/clock_test.cc +++ b/util/misc/clock_test.cc @@ -20,7 +20,7 @@ #include "base/format_macros.h" #include "base/logging.h" -#include "base/macros.h" +#include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "gtest/gtest.h" @@ -83,7 +83,7 @@ TEST(Clock, SleepNanoseconds) { static_cast(5E7), // 50 milliseconds }; - for (size_t index = 0; index < arraysize(kTestData); ++index) { + for (size_t index = 0; index < base::size(kTestData); ++index) { const uint64_t nanoseconds = kTestData[index]; SCOPED_TRACE(base::StringPrintf( "index %zu, nanoseconds %" PRIu64, index, nanoseconds)); diff --git a/util/misc/from_pointer_cast_test.cc b/util/misc/from_pointer_cast_test.cc index 1a6850ff..ce53de54 100644 --- a/util/misc/from_pointer_cast_test.cc +++ b/util/misc/from_pointer_cast_test.cc @@ -41,7 +41,7 @@ using FromPointerCastTestTypes = testing::Types; -TYPED_TEST_CASE(FromPointerCastTest, FromPointerCastTestTypes); +TYPED_TEST_SUITE(FromPointerCastTest, FromPointerCastTestTypes); TYPED_TEST(FromPointerCastTest, ToSigned) { EXPECT_EQ(FromPointerCast(nullptr), 0); diff --git a/util/misc/lexing.cc b/util/misc/lexing.cc index 6c318654..5e8fa304 100644 --- a/util/misc/lexing.cc +++ b/util/misc/lexing.cc @@ -32,10 +32,10 @@ namespace { bool ConvertStringToNumber(const base::StringPiece& input, type* value) { \ return function(input, value); \ } -MAKE_ADAPTER(int, base::StringToInt); -MAKE_ADAPTER(unsigned int, base::StringToUint); -MAKE_ADAPTER(int64_t, base::StringToInt64); -MAKE_ADAPTER(uint64_t, base::StringToUint64); +MAKE_ADAPTER(int, base::StringToInt) +MAKE_ADAPTER(unsigned int, base::StringToUint) +MAKE_ADAPTER(int64_t, base::StringToInt64) +MAKE_ADAPTER(uint64_t, base::StringToUint64) #undef MAKE_ADAPTER } // namespace diff --git a/util/misc/memory_sanitizer.h b/util/misc/memory_sanitizer.h new file mode 100644 index 00000000..a3e0d8ee --- /dev/null +++ b/util/misc/memory_sanitizer.h @@ -0,0 +1,27 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_MISC_MEMORY_SANITIZER_H_ +#define CRASHPAD_UTIL_MISC_MEMORY_SANITIZER_H_ + +#include "base/compiler_specific.h" +#include "build/build_config.h" + +#if !defined(MEMORY_SANITIZER) +#if HAS_FEATURE(memory_sanitizer) +#define MEMORY_SANITIZER 1 +#endif // HAS_FEATURE(memory_sanitizer) +#endif // !defined(MEMORY_SANITIZER) + +#endif // CRASHPAD_UTIL_MISC_MEMORY_SANITIZER_H_ diff --git a/util/misc/metrics.cc b/util/misc/metrics.cc index 7d191f71..bda90313 100644 --- a/util/misc/metrics.cc +++ b/util/misc/metrics.cc @@ -16,6 +16,7 @@ #include "base/metrics/histogram_functions.h" #include "base/metrics/histogram_macros.h" +#include "base/numerics/safe_conversions.h" #include "build/build_config.h" #if defined(OS_MACOSX) @@ -55,43 +56,40 @@ void ExceptionProcessing(ExceptionProcessingState state) { // static void Metrics::CrashReportPending(PendingReportReason reason) { UMA_HISTOGRAM_ENUMERATION( - "Crashpad.CrashReportPending", - static_cast(reason), - static_cast(PendingReportReason::kMaxValue)); + "Crashpad.CrashReportPending", reason, PendingReportReason::kMaxValue); } // static void Metrics::CrashReportSize(FileOffset size) { - UMA_HISTOGRAM_CUSTOM_COUNTS( - "Crashpad.CrashReportSize", size, 0, 20 * 1024 * 1024, 50); + UMA_HISTOGRAM_CUSTOM_COUNTS("Crashpad.CrashReportSize", + base::saturated_cast(size), + 0, + 20 * 1024 * 1024, + 50); } // static void Metrics::CrashUploadAttempted(bool successful) { - UMA_HISTOGRAM_COUNTS("Crashpad.CrashUpload.AttemptSuccessful", - static_cast(successful)); + UMA_HISTOGRAM_COUNTS("Crashpad.CrashUpload.AttemptSuccessful", successful); } // static void Metrics::CrashUploadSkipped(CrashSkippedReason reason) { UMA_HISTOGRAM_ENUMERATION( - "Crashpad.CrashUpload.Skipped", - static_cast(reason), - static_cast(CrashSkippedReason::kMaxValue)); + "Crashpad.CrashUpload.Skipped", reason, CrashSkippedReason::kMaxValue); } // static void Metrics::ExceptionCaptureResult(CaptureResult result) { ExceptionProcessing(ExceptionProcessingState::kFinished); - UMA_HISTOGRAM_ENUMERATION("Crashpad.ExceptionCaptureResult", - static_cast(result), - static_cast(CaptureResult::kMaxValue)); + UMA_HISTOGRAM_ENUMERATION( + "Crashpad.ExceptionCaptureResult", result, CaptureResult::kMaxValue); } // static void Metrics::ExceptionCode(uint32_t exception_code) { base::UmaHistogramSparse("Crashpad.ExceptionCode." METRICS_OS_NAME, - static_cast(exception_code)); + exception_code); } // static @@ -102,15 +100,14 @@ void Metrics::ExceptionEncountered() { // static void Metrics::HandlerLifetimeMilestone(LifetimeMilestone milestone) { UMA_HISTOGRAM_ENUMERATION("Crashpad.HandlerLifetimeMilestone", - static_cast(milestone), - static_cast(LifetimeMilestone::kMaxValue)); + milestone, + LifetimeMilestone::kMaxValue); } // static void Metrics::HandlerCrashed(uint32_t exception_code) { base::UmaHistogramSparse( - "Crashpad.HandlerCrash.ExceptionCode." METRICS_OS_NAME, - static_cast(exception_code)); + "Crashpad.HandlerCrash.ExceptionCode." METRICS_OS_NAME, exception_code); } } // namespace crashpad diff --git a/util/misc/metrics.h b/util/misc/metrics.h index 8046497e..d9763812 100644 --- a/util/misc/metrics.h +++ b/util/misc/metrics.h @@ -139,6 +139,9 @@ class Metrics { //! \brief Sanitization caused this crash dump to be skipped. kSkippedDueToSanitization = 11, + //! \brief Failure to open a memfd caused this crash dump to be skipped. + kOpenMemfdFailed = 12, + //! \brief The number of values in this enumeration; not a valid value. kMaxValue }; diff --git a/util/misc/paths_fuchsia.cc b/util/misc/paths_fuchsia.cc index 09084483..6dea6337 100644 --- a/util/misc/paths_fuchsia.cc +++ b/util/misc/paths_fuchsia.cc @@ -25,7 +25,7 @@ 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 + // https://fuchsia.googlesource.com/docs/+/master/the-book/namespaces.md#typical-directory-structure // . *path = base::FilePath("/pkg/bin/app"); return true; diff --git a/util/misc/paths_win.cc b/util/misc/paths_win.cc index 4c402fe9..f05bdcf4 100644 --- a/util/misc/paths_win.cc +++ b/util/misc/paths_win.cc @@ -17,6 +17,7 @@ #include #include "base/logging.h" +#include "base/stl_util.h" namespace crashpad { @@ -24,11 +25,13 @@ namespace crashpad { bool Paths::Executable(base::FilePath* path) { wchar_t executable_path[_MAX_PATH]; unsigned int len = - GetModuleFileName(nullptr, executable_path, arraysize(executable_path)); + GetModuleFileName(nullptr, + executable_path, + static_cast(base::size(executable_path))); if (len == 0) { PLOG(ERROR) << "GetModuleFileName"; return false; - } else if (len >= arraysize(executable_path)) { + } else if (len >= base::size(executable_path)) { LOG(ERROR) << "GetModuleFileName"; return false; } diff --git a/util/misc/pdb_structures.cc b/util/misc/pdb_structures.cc index c62f11c0..e904dc63 100644 --- a/util/misc/pdb_structures.cc +++ b/util/misc/pdb_structures.cc @@ -18,5 +18,6 @@ namespace crashpad { const uint32_t CodeViewRecordPDB20::kSignature; const uint32_t CodeViewRecordPDB70::kSignature; +const uint32_t CodeViewRecordBuildID::kSignature; } // namespace crashpad diff --git a/util/misc/pdb_structures.h b/util/misc/pdb_structures.h index d0cc9a32..834cfdc8 100644 --- a/util/misc/pdb_structures.h +++ b/util/misc/pdb_structures.h @@ -90,12 +90,7 @@ struct CodeViewRecordPDB70 { // UUID has a constructor, which makes it non-POD, which makes this structure // non-POD. In order for the default constructor to zero-initialize other // members, an explicit constructor must be provided. - CodeViewRecordPDB70() - : signature(), - uuid(), - age(), - pdb_name() { - } + CodeViewRecordPDB70() : signature(), uuid(), age(), pdb_name() {} //! \brief The magic number identifying this structure version, stored in //! #signature. @@ -127,6 +122,27 @@ struct CodeViewRecordPDB70 { uint8_t pdb_name[1]; }; +//! \brief A CodeView record containing an ELF build-id. +//! +//! This identifier comes from the ELF section `NT_GNU_BUILD_ID`. +struct CodeViewRecordBuildID { + //! \brief The magic number identifying this structure version, stored in + //! #signature. + //! + //! In a hex dump, this will appear as “LEpB” when produced by a little-endian + //! machine. + static const uint32_t kSignature = 'BpEL'; + + //! \brief The magic number identifying this structure version, the value of + //! #kSignature. + uint32_t signature; + + //! \brief The build ID for this object. + //! + //! This usually comes from `NT_GNU_BUILD_ID` on ELF objects. + uint8_t build_id[1]; +}; + } // namespace crashpad #endif // CRASHPAD_UTIL_MISC_PDB_STRUCTURES_H_ diff --git a/util/misc/random_string_test.cc b/util/misc/random_string_test.cc index b0866c06..f5f0f325 100644 --- a/util/misc/random_string_test.cc +++ b/util/misc/random_string_test.cc @@ -18,7 +18,7 @@ #include -#include "base/macros.h" +#include "base/stl_util.h" #include "gtest/gtest.h" namespace crashpad { @@ -33,7 +33,7 @@ TEST(RandomString, RandomString) { const std::string allowed_characters("ABCDEFGHIJKLMNOPQRSTUVWXYZ"); size_t character_counts[26] = {}; - ASSERT_EQ(allowed_characters.size(), arraysize(character_counts)); + ASSERT_EQ(allowed_characters.size(), base::size(character_counts)); std::set strings; @@ -61,7 +61,7 @@ TEST(RandomString, RandomString) { // Make sure every character appears at least once. It is possible, but // extremely unlikely, for a character to not appear at all. for (size_t character_index = 0; - character_index < arraysize(character_counts); + character_index < base::size(character_counts); ++character_index) { EXPECT_GT(character_counts[character_index], 0u) << allowed_characters[character_index]; diff --git a/util/misc/time.h b/util/misc/time.h index aabe8e64..dffe1a8a 100644 --- a/util/misc/time.h +++ b/util/misc/time.h @@ -69,6 +69,14 @@ void GetTimeOfDay(timeval* tv); #endif // OS_WIN +#if defined(OS_LINUX) || defined(OS_ANDROID) || DOXYGEN +//! \brief Get the kernel boot time. Subsequent calls to this function may +//! return different results due to the system clock being changed or +//! imprecision in measuring the boot time. +//! \return `true` on success. Otherwise, `false` with a message logged. +bool GetBootTime(timespec* ts); +#endif // OS_LINUX || OS_ANDROID || DOXYGEN + } // namespace crashpad #endif // CRASHPAD_UTIL_MISC_TIME_H_ diff --git a/util/misc/time_linux.cc b/util/misc/time_linux.cc new file mode 100644 index 00000000..bc73533c --- /dev/null +++ b/util/misc/time_linux.cc @@ -0,0 +1,38 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/misc/time.h" + +#include "base/logging.h" + +namespace crashpad { + +bool GetBootTime(timespec* boot_time) { + timespec uptime; + if (clock_gettime(CLOCK_BOOTTIME, &uptime) != 0) { + PLOG(ERROR) << "clock_gettime"; + return false; + } + + timespec current_time; + if (clock_gettime(CLOCK_REALTIME, ¤t_time) != 0) { + PLOG(ERROR) << "clock_gettime"; + return false; + } + + SubtractTimespec(current_time, uptime, boot_time); + return true; +} + +} // namespace crashpad diff --git a/util/misc/uuid_test.cc b/util/misc/uuid_test.cc index c05c5c1b..35f9ecd4 100644 --- a/util/misc/uuid_test.cc +++ b/util/misc/uuid_test.cc @@ -20,8 +20,8 @@ #include #include "base/format_macros.h" -#include "base/macros.h" #include "base/scoped_generic.h" +#include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "gtest/gtest.h" @@ -95,12 +95,12 @@ TEST(UUID, UUID) { ++uuid.data_3; EXPECT_NE(uuid, uuid_2); --uuid.data_3; - for (size_t index = 0; index < arraysize(uuid.data_4); ++index) { + for (size_t index = 0; index < base::size(uuid.data_4); ++index) { ++uuid.data_4[index]; EXPECT_NE(uuid, uuid_2); --uuid.data_4[index]; } - for (size_t index = 0; index < arraysize(uuid.data_5); ++index) { + for (size_t index = 0; index < base::size(uuid.data_5); ++index) { ++uuid.data_5[index]; EXPECT_NE(uuid, uuid_2); --uuid.data_5[index]; @@ -190,7 +190,7 @@ TEST(UUID, FromString) { uuid_zero.InitializeToZero(); const std::string empty_uuid = uuid_zero.ToString(); - for (size_t index = 0; index < arraysize(kCases); ++index) { + for (size_t index = 0; index < base::size(kCases); ++index) { const TestCase& test_case = kCases[index]; SCOPED_TRACE(base::StringPrintf( "index %" PRIuS ": %s", index, test_case.uuid_string)); @@ -226,7 +226,7 @@ TEST(UUID, FromString) { }; // clang-format on EXPECT_TRUE(uuid.InitializeFromString( - base::StringPiece16(kChar16UUID, arraysize(kChar16UUID)))); + base::StringPiece16(kChar16UUID, base::size(kChar16UUID)))); EXPECT_EQ(uuid.ToString(), "f32e5bdc-2681-4c73-a4e6-333ffd33b333"); #if defined(OS_WIN) diff --git a/util/net/generate_test_server_key.py b/util/net/generate_test_server_key.py index 6e6340e3..3db08ade 100755 --- a/util/net/generate_test_server_key.py +++ b/util/net/generate_test_server_key.py @@ -16,13 +16,35 @@ import os import subprocess +import sys -# GN requires a Python script for actions, so this just wraps the openssl -# command needed to generate a test private key and a certificate. These names -# must correspond to what TestPaths::BuildArtifact() constructs. -key = 'crashpad_util_test_key.pem' -cert = 'crashpad_util_test_cert.pem' -subprocess.check_call( - ['openssl', 'req', '-x509', '-nodes', '-subj', '/CN=localhost', - '-days', '365', '-newkey', 'rsa:2048', '-keyout', key, '-out', cert], - stderr=open(os.devnull, 'w')) +testdata = os.path.join(os.path.dirname(__file__), 'testdata') +key = os.path.join(testdata, 'crashpad_util_test_key.pem') +cert = os.path.join(testdata, 'crashpad_util_test_cert.pem') + +with open(cert, 'w') as cert_file, open(key, 'w') as key_file: + MESSAGE = ('DO NOT EDIT: This file was auto-generated by ' + __file__ + + '\n\n') + cert_file.write(MESSAGE) + key_file.write(MESSAGE) + + proc = subprocess.Popen([ + 'openssl', 'req', '-x509', '-nodes', '-subj', '/CN=localhost', '-days', + '3650', '-newkey', 'rsa:2048', '-keyout', '-' + ], + stderr=open(os.devnull, 'w'), + stdout=subprocess.PIPE) + + contents = proc.communicate()[0] + dest = sys.stderr + for line in contents.splitlines(True): + if line.startswith("-----BEGIN PRIVATE KEY-----"): + dest = key_file + elif line.startswith("-----BEGIN CERTIFICATE-----"): + dest = cert_file + elif line.startswith("-----END"): + dest.write(line) + dest = sys.stderr + continue + + dest.write(line) diff --git a/util/net/http_body_test.cc b/util/net/http_body_test.cc index e48cbf91..1e5a4796 100644 --- a/util/net/http_body_test.cc +++ b/util/net/http_body_test.cc @@ -207,9 +207,9 @@ TEST_P(CompositeHTTPBodyStreamBufferSize, StringsAndFile) { EXPECT_EQ(actual_string, expected_string); } -INSTANTIATE_TEST_CASE_P(VariableBufferSize, - CompositeHTTPBodyStreamBufferSize, - testing::Values(1, 2, 9, 16, 31, 128, 1024)); +INSTANTIATE_TEST_SUITE_P(VariableBufferSize, + CompositeHTTPBodyStreamBufferSize, + testing::Values(1, 2, 9, 16, 31, 128, 1024)); } // namespace } // namespace test diff --git a/util/net/http_transport.h b/util/net/http_transport.h index f91a5561..acd4e442 100644 --- a/util/net/http_transport.h +++ b/util/net/http_transport.h @@ -90,7 +90,7 @@ class HTTPTransport { //! if the response body is not required. //! //! \return Whether or not the request was successful, defined as returning - //! a HTTP status 200 (OK) code. + //! a HTTP status code in the range 200-203 (inclusive). virtual bool ExecuteSynchronously(std::string* response_body) = 0; protected: diff --git a/util/net/http_transport_mac.mm b/util/net/http_transport_mac.mm index 8d5f78cc..a433bb35 100644 --- a/util/net/http_transport_mac.mm +++ b/util/net/http_transport_mac.mm @@ -293,7 +293,7 @@ bool HTTPTransportMac::ExecuteSynchronously(std::string* response_body) { return false; } NSInteger http_status = [http_response statusCode]; - if (http_status != 200) { + if (http_status < 200 || http_status > 203) { LOG(ERROR) << base::StringPrintf("HTTP status %ld", implicit_cast(http_status)); return false; diff --git a/util/net/http_transport_socket.cc b/util/net/http_transport_socket.cc index f0e2dc14..4dd01b6e 100644 --- a/util/net/http_transport_socket.cc +++ b/util/net/http_transport_socket.cc @@ -24,6 +24,7 @@ #include "base/numerics/safe_conversions.h" #include "base/posix/eintr_wrapper.h" #include "base/scoped_generic.h" +#include "base/stl_util.h" #include "base/strings/string_number_conversions.h" #include "base/strings/stringprintf.h" #include "util/file/file_io.h" @@ -57,8 +58,7 @@ struct ScopedAddrinfoTraits { static addrinfo* InvalidValue() { return nullptr; } static void Free(addrinfo* ai) { freeaddrinfo(ai); } }; -using ScopedAddrinfo = - base::ScopedGeneric; +using ScopedAddrinfo = base::ScopedGeneric; class Stream { public: @@ -81,7 +81,7 @@ class FdStream : public Stream { return LoggingReadFileExactly(fd_, data, size); } - bool LoggingReadToEOF(std::string* result) override{ + bool LoggingReadToEOF(std::string* result) override { return crashpad::LoggingReadToEOF(fd_, result); } @@ -366,7 +366,7 @@ bool WriteRequest(Stream* stream, FileOperationResult data_bytes; do { - constexpr size_t kCRLFSize = arraysize(kCRLFTerminator) - 1; + constexpr size_t kCRLFSize = base::size(kCRLFTerminator) - 1; struct __attribute__((packed)) { char size[8]; char crlf[2]; @@ -394,11 +394,13 @@ bool WriteRequest(Stream* stream, // 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); + char tmp[9]; + int rv = snprintf(tmp, + sizeof(tmp), + "%08x", + base::checked_cast(data_bytes)); DCHECK_EQ(static_cast(rv), sizeof(buf.size)); + strncpy(buf.size, tmp, sizeof(buf.size)); DCHECK_NE(buf.size[sizeof(buf.size) - 1], '\0'); memcpy(&buf.crlf[0], kCRLFTerminator, kCRLFSize); @@ -457,10 +459,18 @@ bool ReadResponseLine(Stream* stream) { LOG(ERROR) << "ReadLine"; return false; } - static constexpr const char kHttp10[] = "HTTP/1.0 200 "; - static constexpr const char kHttp11[] = "HTTP/1.1 200 "; - return StartsWith(response_line, kHttp10, strlen(kHttp10)) || - StartsWith(response_line, kHttp11, strlen(kHttp11)); + static constexpr const char kHttp10[] = "HTTP/1.0 "; + static constexpr const char kHttp11[] = "HTTP/1.1 "; + if (!(StartsWith(response_line, kHttp10, strlen(kHttp10)) || + StartsWith(response_line, kHttp11, strlen(kHttp11))) || + response_line.size() < strlen(kHttp10) + 3 || + response_line.at(strlen(kHttp10) + 3) != ' ') { + return false; + } + unsigned int http_status = 0; + return base::StringToUint(response_line.substr(strlen(kHttp10), 3), + &http_status) && + http_status >= 200 && http_status <= 203; } bool ReadResponseHeaders(Stream* stream, HTTPHeaders* headers) { @@ -537,7 +547,8 @@ bool HTTPTransportSocket::ExecuteSynchronously(std::string* response_body) { } #if !defined(CRASHPAD_USE_BORINGSSL) - CHECK(scheme == "http"); + CHECK(scheme == "http") << "Got " << scheme << " for scheme in '" << url() + << "'"; #endif base::ScopedFD sock(CreateSocket(hostname, port)); diff --git a/util/net/http_transport_test.cc b/util/net/http_transport_test.cc index 7b5f41df..d6a7675f 100644 --- a/util/net/http_transport_test.cc +++ b/util/net/http_transport_test.cc @@ -54,10 +54,10 @@ std::string ToUTF8IfWin(const std::string& x) { class HTTPTransportTestFixture : public MultiprocessExec { public: - using RequestValidator = - void(*)(HTTPTransportTestFixture*, const std::string&); + using RequestValidator = void (*)(HTTPTransportTestFixture*, + const std::string&); - HTTPTransportTestFixture(const base::FilePath::StringType& scheme, + HTTPTransportTestFixture(const std::string& scheme, const HTTPHeaders& headers, std::unique_ptr body_stream, uint16_t http_response_code, @@ -74,22 +74,21 @@ class HTTPTransportTestFixture : public MultiprocessExec { #if defined(OS_WIN) FILE_PATH_LITERAL(".exe") #endif - ); + ); - if (ToUTF8IfWin(scheme) == "http") { + if (scheme == "http") { scheme_and_host_ = "http://localhost"; SetChildCommand(server_path, nullptr); } else { std::vector args; - cert_ = TestPaths::BuildArtifact(FILE_PATH_LITERAL("util"), - FILE_PATH_LITERAL("cert"), - TestPaths::FileType::kCertificate); + cert_ = TestPaths::TestDataRoot().Append( + FILE_PATH_LITERAL("util/net/testdata/crashpad_util_test_cert.pem")); args.push_back(ToUTF8IfWin(cert_.value())); - args.emplace_back(ToUTF8IfWin( - TestPaths::BuildArtifact(FILE_PATH_LITERAL("util"), - FILE_PATH_LITERAL("key"), - TestPaths::FileType::kCertificate) - .value())); + args.emplace_back( + ToUTF8IfWin(TestPaths::TestDataRoot() + .Append(FILE_PATH_LITERAL( + "util/net/testdata/crashpad_util_test_key.pem")) + .value())); SetChildCommand(server_path, &args); scheme_and_host_ = "https://localhost"; } @@ -117,9 +116,8 @@ class HTTPTransportTestFixture : public MultiprocessExec { // 200. const std::string random_string = RandomString(); - ASSERT_TRUE(LoggingWriteFile(WritePipeHandle(), - random_string.c_str(), - random_string.size())); + ASSERT_TRUE(LoggingWriteFile( + WritePipeHandle(), random_string.c_str(), random_string.size())); // Now execute the HTTP request. std::unique_ptr transport(HTTPTransport::Create()); @@ -137,7 +135,7 @@ class HTTPTransportTestFixture : public MultiprocessExec { std::string response_body; bool success = transport->ExecuteSynchronously(&response_body); - if (response_code_ == 200) { + if (response_code_ >= 200 && response_code_ <= 203) { EXPECT_TRUE(success); std::string expect_response_body = random_string + "\r\n"; EXPECT_EQ(response_body, expect_response_body); @@ -246,8 +244,7 @@ void ValidFormData(HTTPTransportTestFixture* fixture, EXPECT_EQ(request.substr(body_start), expected); } -class HTTPTransport - : public testing::TestWithParam {}; +class HTTPTransport : public testing::TestWithParam {}; TEST_P(HTTPTransport, ValidFormData) { HTTPMultipartBuilder builder; @@ -257,8 +254,8 @@ TEST_P(HTTPTransport, ValidFormData) { HTTPHeaders headers; builder.PopulateContentHeaders(&headers); - HTTPTransportTestFixture test(GetParam(), - headers, builder.GetBodyStream(), 200, &ValidFormData); + HTTPTransportTestFixture test( + GetParam(), headers, builder.GetBodyStream(), 200, &ValidFormData); test.Run(); } @@ -289,8 +286,8 @@ TEST_P(HTTPTransport, ErrorResponse) { HTTPMultipartBuilder builder; HTTPHeaders headers; headers[kContentType] = kTextPlain; - HTTPTransportTestFixture test(GetParam(), headers, builder.GetBodyStream(), - 404, &ErrorResponse); + HTTPTransportTestFixture test( + GetParam(), headers, builder.GetBodyStream(), 404, &ErrorResponse); test.Run(); } @@ -321,13 +318,12 @@ TEST_P(HTTPTransport, UnchunkedPlainText) { headers[kContentType] = kTextPlain; headers[kContentLength] = base::StringPrintf("%" PRIuS, strlen(kTextBody)); - HTTPTransportTestFixture test(GetParam(), - headers, std::move(body_stream), 200, &UnchunkedPlainText); + HTTPTransportTestFixture test( + GetParam(), headers, std::move(body_stream), 200, &UnchunkedPlainText); test.Run(); } -void RunUpload33k(const base::FilePath::StringType& scheme, - bool has_content_length) { +void RunUpload33k(const std::string& scheme, bool has_content_length) { // On macOS, NSMutableURLRequest winds up calling into a CFReadStream’s Read() // callback with a 32kB buffer. Make sure that it’s able to get everything // when enough is available to fill this buffer, requiring more than one @@ -364,20 +360,29 @@ TEST_P(HTTPTransport, Upload33k_LengthUnknown) { RunUpload33k(GetParam(), false); } -#if defined(CRASHPAD_USE_BORINGSSL) +// This should be on for Fuchsia, but DX-382. Debug and re-enabled. +#if defined(CRASHPAD_USE_BORINGSSL) && !defined(OS_FUCHSIA) // The test server requires BoringSSL or OpenSSL, so https in tests can only be // enabled where that's readily available. Additionally on Linux, the bots fail // lacking libcrypto.so.1.1, so disabled there for now. On Mac, they could also // likely be enabled relatively easily, if HTTPTransportMac learned to respect // the user-supplied cert. -INSTANTIATE_TEST_CASE_P(HTTPTransport, - HTTPTransport, - testing::Values(FILE_PATH_LITERAL("http"), - FILE_PATH_LITERAL("https"))); +// +// If tests with boringssl are failing because of expired certificates, try +// re-running generate_test_server_key.py. +INSTANTIATE_TEST_SUITE_P(HTTPTransport, + HTTPTransport, + testing::Values("http", "https"), + [](const testing::TestParamInfo& info) { + return info.param; + }); #else -INSTANTIATE_TEST_CASE_P(HTTPTransport, - HTTPTransport, - testing::Values(FILE_PATH_LITERAL("http"))); +INSTANTIATE_TEST_SUITE_P(HTTPTransport, + HTTPTransport, + testing::Values("http"), + [](const testing::TestParamInfo& info) { + return info.param; + }); #endif } // namespace diff --git a/util/net/http_transport_win.cc b/util/net/http_transport_win.cc index 18d343cc..06ecc4f3 100644 --- a/util/net/http_transport_win.cc +++ b/util/net/http_transport_win.cc @@ -25,14 +25,15 @@ #include "base/logging.h" #include "base/numerics/safe_conversions.h" #include "base/scoped_generic.h" +#include "base/stl_util.h" #include "base/strings/string_number_conversions.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "build/build_config.h" #include "package.h" #include "util/file/file_io.h" -#include "util/numeric/safe_assignment.h" #include "util/net/http_body.h" +#include "util/numeric/safe_assignment.h" #include "util/win/module_version.h" namespace crashpad { @@ -65,6 +66,8 @@ std::string UserAgent() { user_agent.append("x86"); #elif defined(ARCH_CPU_X86_64) user_agent.append("x64"); +#elif defined(ARCH_CPU_ARM64) + user_agent.append("arm64"); #else #error Port #endif @@ -93,8 +96,8 @@ std::string WinHttpMessage(const char* extra) { error_code, 0, msgbuf, - arraysize(msgbuf), - NULL); + static_cast(base::size(msgbuf)), + nullptr); if (!len) { return base::StringPrintf("%s: error 0x%lx while retrieving error 0x%lx", extra, @@ -375,7 +378,7 @@ bool HTTPTransportWin::ExecuteSynchronously(std::string* response_body) { return false; } - if (status_code != 200) { + if (status_code < 200 || status_code > 203) { LOG(ERROR) << base::StringPrintf("HTTP status %lu", status_code); return false; } diff --git a/util/net/testdata/crashpad_util_test_cert.pem b/util/net/testdata/crashpad_util_test_cert.pem new file mode 100644 index 00000000..5a2261e2 --- /dev/null +++ b/util/net/testdata/crashpad_util_test_cert.pem @@ -0,0 +1,21 @@ +DO NOT EDIT: This file was auto-generated by ./util/net/generate_test_server_key.py + +-----BEGIN CERTIFICATE----- +MIIDCDCCAfCgAwIBAgITF9h5BaPfzje590HrE1UF+f9PVDANBgkqhkiG9w0BAQsF +ADAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwHhcNMTkxMDMwMjIyNDE4WhcNMjkxMDI3 +MjIyNDE4WjAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUA +A4IBDwAwggEKAoIBAQCrT0iXfixTPH6nBxgvCgiKkSTeVTSvfhcoUYA7yPZj/1XH +VYfECoGyvFN4/5g/GL4c721XZmI92Twg2Oa3HksRY8VlNXGNwqbt+wftSLOz+dOE +bsJ3+NsBtXuaIphFPA9u0h+yrplBtM/dxLTW5v8e3OuEMBXa+94dp4y78u21UkzM +uWB66tduT7/f0S9P68XhksYwwwvCn5lu258wHKNM5mb4CTwOcUgyyprlS2FS3Fsx +pIsg9wDfQy5rHINxcZGZZ8Ht4KOJ7egwhfiP4XEiLK6+5DZoOQtrkEUFfLL4i5sx ++eOeD12GP+Dxs6KHYoejGftNx/7U0E2GVMWwWzstAgMBAAGjUzBRMB0GA1UdDgQW +BBT8y2bnyty/YWcvzlE9JIAPG7/q4DAfBgNVHSMEGDAWgBT8y2bnyty/YWcvzlE9 +JIAPG7/q4DAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCF+R21 +Cd1fgITfhYJE7JAV3jLfact5h/2MP5UwgtODfZt2Idwxs8qqwPB0g7WQqEjrPfTv +6y90AEwr2L8e+wXGnTKtWelCqzztFGov4Hj+rd158eOmEo0IPGrx8dwqLQbw91uU +cq4onf5iuHkOt99TmIqVYn2zaOHbOF2YYyU052X+9XE/Z5fhibX5THfEG3w0+XM6 +aKEGIM/MxfVaLA8yDXFxhDXHBrH+QAAXTQaC8exnp+po/psyJARD0sM509MeTdAv +po9JyIzesNAsLW4I7kL8I5i8e+WN79lX2jgwaWMxPmHadYN3mtoltpmFM40oFN6q +8wjuU07eN/G79c3B +-----END CERTIFICATE----- diff --git a/util/net/testdata/crashpad_util_test_key.pem b/util/net/testdata/crashpad_util_test_key.pem new file mode 100644 index 00000000..b0d69731 --- /dev/null +++ b/util/net/testdata/crashpad_util_test_key.pem @@ -0,0 +1,30 @@ +DO NOT EDIT: This file was auto-generated by ./util/net/generate_test_server_key.py + +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCrT0iXfixTPH6n +BxgvCgiKkSTeVTSvfhcoUYA7yPZj/1XHVYfECoGyvFN4/5g/GL4c721XZmI92Twg +2Oa3HksRY8VlNXGNwqbt+wftSLOz+dOEbsJ3+NsBtXuaIphFPA9u0h+yrplBtM/d +xLTW5v8e3OuEMBXa+94dp4y78u21UkzMuWB66tduT7/f0S9P68XhksYwwwvCn5lu +258wHKNM5mb4CTwOcUgyyprlS2FS3FsxpIsg9wDfQy5rHINxcZGZZ8Ht4KOJ7egw +hfiP4XEiLK6+5DZoOQtrkEUFfLL4i5sx+eOeD12GP+Dxs6KHYoejGftNx/7U0E2G +VMWwWzstAgMBAAECggEANEgJDnrqSijfOlh27/wu6SMszlHQt3JS3PIqoZROZexK +IICg45qVRJgnHXlb3H3Pn4MOqqrLdraynA+2MdKj9FWvq5io5CuwyFZhb/BNL7Mh +83veC8E+DYJ2i27da9vNlfO4ys5wZVYqTjM3QZLT73Zaxkfqk59khUZaNA4Kr9hD +EBs9gaDMATyIAN5eAbYTVxgobZ+7OjCNmQ46x2KOg6Wwg/ZQEXtqFoDB3aCNVfTz +OxlLb5XWF9PvqW0p1wFpF0XEDipkVtjorjp3mHXRuq0gS9Gev1ChQej1yVqMee9J +jnVK0k7qZw4WLStxBjHqWlBnYLcXLb4f2c6cssA6YQKBgQDfqtYFjU3HCeoRaAoP +MWTSW2PX8fitnp0O4e1yHymDgtirepvPL8pa7rTBkHuR6lfXNjY0qD0NPsbr81nG +kNv/btLOK1YPw6f468S6DMsOzGPIWWE+jkhF/1iUTMUQLvOM3lCLEi0ayZ26yx1T +F2wO2u1kGc1eJ8wn6TccsYiguQKBgQDEEt7eGQMkxLFV4N5e5iunHBvQg+EgfLZZ +bnuftwruqafmCht/BZgmKQD+e6B6h3nC/iaKET101zpWL95x7Ayd1+mg6Rmdzl87 +ctftScrNLYUTl36BuhP82Y9HvLgf20xrEwBGivc0QtqhMAz/DJoknEM/29LmWEsZ +VwvWQxhsFQKBgQCrOcJMT8d6Fznsh2QkC2EutK3ztBb2+xUrPoQjOH30YqfyZpN/ +Agv8nv8bq7sdknQamjLXDvBmAmgQW6SfoWf53OJe2Mgym0stAXkCIScWNhwxVVNf +q1bi1z79kOPPptHmRo8MWCbVegFY7YOOh8C+gpT3a9VPPlJJP31kZvi8aQKBgDcD +ZGzEb9FdLrR9x2axBgZ5KIS0u/G1jCRDj4Qcg4C7MVSl+VkGZM4wKws7/KbkZBGF +5aJPfALQcJnGDI/CPzf6YJ65SGqygJ3ZdyQo1DIFV5VLqD8Vyo3jLQRfuvmVOjfA +uQ8R5pJPP7CCHuNg0c772RKNxvrCQy/08GlJogyRAoGAK6A/8r/iDRJipJSOZdCl +MreiJvjFOQrORhy+TeDGn02EHK0lijzj1I7SlgfUDLWVnQpBlk12wV6aHGUj2NlG +Ctk15MFs6gggPV1Fmt6PeviE1e9E4+SDWX35Jhe8JDIqxdgZ0HW/S9Nl4Xt3owhw +/DSneMQd7X0Tdq7lesJjd3o= +-----END PRIVATE KEY----- diff --git a/util/net/tls.gni b/util/net/tls.gni new file mode 100644 index 00000000..eb968450 --- /dev/null +++ b/util/net/tls.gni @@ -0,0 +1,23 @@ +# Copyright 2018 The Crashpad Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("../../build/crashpad_buildconfig.gni") + +declare_args() { + # TODO(scottmg): https://crbug.com/crashpad/266 fuchsia:DX-690: BoringSSL + # was removed from the Fuchsia SDK. Re-enable it when we have a way to acquire + # a BoringSSL lib again. + crashpad_use_boringssl_for_http_transport_socket = + crashpad_is_in_fuchsia || (crashpad_is_linux && crashpad_is_in_chromium) +} diff --git a/util/numeric/checked_address_range_test.cc b/util/numeric/checked_address_range_test.cc index e6bd9ecd..08bc551c 100644 --- a/util/numeric/checked_address_range_test.cc +++ b/util/numeric/checked_address_range_test.cc @@ -19,7 +19,7 @@ #include #include "base/format_macros.h" -#include "base/macros.h" +#include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "build/build_config.h" #include "gtest/gtest.h" @@ -119,7 +119,7 @@ TEST(CheckedAddressRange, IsValid) { {0xffffffffffffffff, 1, kInvalid}, }; - for (size_t index = 0; index < arraysize(kTestData); ++index) { + for (size_t index = 0; index < base::size(kTestData); ++index) { const auto& testcase = kTestData[index]; SCOPED_TRACE(base::StringPrintf("index %" PRIuS ", base 0x%" PRIx64 ", size 0x%" PRIx64, @@ -170,7 +170,7 @@ TEST(CheckedAddressRange, ContainsValue) { CheckedAddressRange parent_range_32(false, 0x2000, 0x1000); ASSERT_TRUE(parent_range_32.IsValid()); - for (size_t index = 0; index < arraysize(kTestData); ++index) { + for (size_t index = 0; index < base::size(kTestData); ++index) { const auto& testcase = kTestData[index]; SCOPED_TRACE(base::StringPrintf( "index %" PRIuS ", value 0x%" PRIx64, index, testcase.value)); @@ -227,7 +227,7 @@ TEST(CheckedAddressRange, ContainsRange) { CheckedAddressRange parent_range_32(false, 0x2000, 0x1000); ASSERT_TRUE(parent_range_32.IsValid()); - for (size_t index = 0; index < arraysize(kTestData); ++index) { + for (size_t index = 0; index < base::size(kTestData); ++index) { const auto& testcase = kTestData[index]; SCOPED_TRACE(base::StringPrintf("index %" PRIuS ", base 0x%" PRIx64 ", size 0x%" PRIx64, diff --git a/util/numeric/checked_range_test.cc b/util/numeric/checked_range_test.cc index 04f6bb1f..9d611e8d 100644 --- a/util/numeric/checked_range_test.cc +++ b/util/numeric/checked_range_test.cc @@ -20,7 +20,7 @@ #include #include "base/format_macros.h" -#include "base/macros.h" +#include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "gtest/gtest.h" @@ -78,7 +78,7 @@ TEST(CheckedRange, IsValid) { {0xffffffff, 0xffffffff, false}, }; - for (size_t index = 0; index < arraysize(kUnsignedTestData); ++index) { + for (size_t index = 0; index < base::size(kUnsignedTestData); ++index) { const auto& testcase = kUnsignedTestData[index]; SCOPED_TRACE(base::StringPrintf("unsigned index %" PRIuS ", base 0x%x, size 0x%x", @@ -140,7 +140,7 @@ TEST(CheckedRange, IsValid) { {-1, 0xffffffff, false}, }; - for (size_t index = 0; index < arraysize(kSignedTestData); ++index) { + for (size_t index = 0; index < base::size(kSignedTestData); ++index) { const auto& testcase = kSignedTestData[index]; SCOPED_TRACE(base::StringPrintf("signed index %" PRIuS ", base 0x%x, size 0x%x", @@ -186,7 +186,7 @@ TEST(CheckedRange, ContainsValue) { CheckedRange parent_range(0x2000, 0x1000); ASSERT_TRUE(parent_range.IsValid()); - for (size_t index = 0; index < arraysize(kTestData); ++index) { + for (size_t index = 0; index < base::size(kTestData); ++index) { const auto& testcase = kTestData[index]; SCOPED_TRACE(base::StringPrintf( "index %" PRIuS ", value 0x%x", index, testcase.value)); @@ -234,7 +234,7 @@ TEST(CheckedRange, ContainsRange) { CheckedRange parent_range(0x2000, 0x1000); ASSERT_TRUE(parent_range.IsValid()); - for (size_t index = 0; index < arraysize(kTestData); ++index) { + for (size_t index = 0; index < base::size(kTestData); ++index) { const auto& testcase = kTestData[index]; SCOPED_TRACE(base::StringPrintf("index %" PRIuS ", base 0x%x, size 0x%x", index, @@ -287,7 +287,7 @@ TEST(CheckedRange, OverlapsRange) { CheckedRange first_range(0x2000, 0x1000); ASSERT_TRUE(first_range.IsValid()); - for (size_t index = 0; index < arraysize(kTestData); ++index) { + for (size_t index = 0; index < base::size(kTestData); ++index) { const auto& testcase = kTestData[index]; SCOPED_TRACE(base::StringPrintf("index %" PRIuS ", base 0x%x, size 0x%x", index, diff --git a/util/posix/close_multiple.cc b/util/posix/close_multiple.cc index 02c8a767..238f158c 100644 --- a/util/posix/close_multiple.cc +++ b/util/posix/close_multiple.cc @@ -25,6 +25,7 @@ #include "base/files/scoped_file.h" #include "base/logging.h" #include "base/posix/eintr_wrapper.h" +#include "base/stl_util.h" #include "base/strings/string_number_conversions.h" #include "build/build_config.h" #include "util/file/directory_reader.h" @@ -152,7 +153,7 @@ void CloseMultipleNowOrOnExec(int fd, int preserve_fd) { int maxfilesperproc; size_t maxfilesperproc_size = sizeof(maxfilesperproc); if (sysctl(oid, - arraysize(oid), + base::size(oid), &maxfilesperproc, &maxfilesperproc_size, nullptr, diff --git a/util/posix/close_stdio.cc b/util/posix/close_stdio.cc index cc9cdac8..02bd4a93 100644 --- a/util/posix/close_stdio.cc +++ b/util/posix/close_stdio.cc @@ -20,6 +20,7 @@ #include "base/files/scoped_file.h" #include "base/logging.h" +#include "base/macros.h" #include "base/posix/eintr_wrapper.h" namespace crashpad { diff --git a/util/posix/double_fork_and_exec.cc b/util/posix/double_fork_and_exec.cc index df74f709..e4ad9988 100644 --- a/util/posix/double_fork_and_exec.cc +++ b/util/posix/double_fork_and_exec.cc @@ -27,9 +27,12 @@ namespace crashpad { bool DoubleForkAndExec(const std::vector& argv, + const std::vector* envp, int preserve_fd, bool use_path, void (*child_function)()) { + DCHECK(!envp || !use_path); + // argv_c contains const char* pointers and is terminated by nullptr. This is // suitable for passing to execv(). Although argv_c is not used in the parent // process, it must be built in the parent process because it’s unsafe to do @@ -41,6 +44,15 @@ bool DoubleForkAndExec(const std::vector& argv, } argv_c.push_back(nullptr); + std::vector envp_c; + if (envp) { + envp_c.reserve(envp->size() + 1); + for (const std::string& variable : *envp) { + envp_c.push_back(variable.c_str()); + } + envp_c.push_back(nullptr); + } + // Double-fork(). The three processes involved are parent, child, and // grandchild. The grandchild will call execv(). The child exits immediately // after spawning the grandchild, so the grandchild becomes an orphan and its @@ -102,6 +114,13 @@ bool DoubleForkAndExec(const std::vector& argv, // const_cast is safe. char* const* argv_for_execv = const_cast(&argv_c[0]); + if (envp) { + // This cast is safe for the same reason that the argv_for_execv cast is. + char* const* envp_for_execv = const_cast(&envp_c[0]); + execve(argv_for_execv[0], argv_for_execv, envp_for_execv); + PLOG(FATAL) << "execve " << argv_for_execv[0]; + } + if (use_path) { execvp(argv_for_execv[0], argv_for_execv); PLOG(FATAL) << "execvp " << argv_for_execv[0]; diff --git a/util/posix/double_fork_and_exec.h b/util/posix/double_fork_and_exec.h index df340d07..02fc0f28 100644 --- a/util/posix/double_fork_and_exec.h +++ b/util/posix/double_fork_and_exec.h @@ -36,6 +36,9 @@ namespace crashpad { //! //! \param[in] argv The argument vector to start the grandchild process with. //! `argv[0]` is used as the path to the executable. +//! \param[in] envp A vector of environment variables of the form `var=value` to +//! be passed to `execve()`. If this value is `nullptr`, the current +//! environment is used. //! \param[in] preserve_fd A file descriptor to be inherited by the grandchild //! process. This file descriptor is inherited in addition to the three file //! descriptors associated with the standard input/output streams. Use `-1` @@ -49,6 +52,9 @@ namespace crashpad { //! that this function will run in the context of a forked process, and must //! be safe for that purpose. //! +//! Setting both \a envp to a value other than `nullptr` and \a use_path to +//! `true` is not currently supported. +//! //! \return `true` on success, and `false` on failure with a message logged. //! Only failures that occur in the parent process that indicate a definite //! failure to start the the grandchild are reported in the return value. @@ -58,6 +64,7 @@ namespace crashpad { //! failures, for example, by observing a failure to perform a successful //! handshake with the grandchild process. bool DoubleForkAndExec(const std::vector& argv, + const std::vector* envp, int preserve_fd, bool use_path, void (*child_function)()); diff --git a/util/posix/process_info.h b/util/posix/process_info.h index 18a9cd93..7017cc64 100644 --- a/util/posix/process_info.h +++ b/util/posix/process_info.h @@ -174,6 +174,7 @@ class ProcessInfo { // multiple successive calls will always produce the same return value and out // parameters. This is necessary for intergration with the Snapshot interface. // See https://crashpad.chromium.org/bug/9. + PtraceConnection* connection_; std::set supplementary_groups_; mutable timeval start_time_; pid_t pid_; diff --git a/util/posix/process_info_linux.cc b/util/posix/process_info_linux.cc index 10c94b36..3ffd96ab 100644 --- a/util/posix/process_info_linux.cc +++ b/util/posix/process_info_linux.cc @@ -20,13 +20,16 @@ #include "base/logging.h" #include "util/file/delimited_file_reader.h" #include "util/file/file_reader.h" +#include "util/file/string_file.h" #include "util/linux/proc_stat_reader.h" #include "util/misc/lexing.h" +#include "util/misc/time.h" namespace crashpad { ProcessInfo::ProcessInfo() - : supplementary_groups_(), + : connection_(), + supplementary_groups_(), start_time_(), pid_(-1), ppid_(-1), @@ -45,16 +48,19 @@ bool ProcessInfo::InitializeWithPtrace(PtraceConnection* connection) { INITIALIZATION_STATE_SET_INITIALIZING(initialized_); DCHECK(connection); + connection_ = connection; pid_ = connection->GetProcessID(); is_64_bit_ = connection->Is64Bit(); { char path[32]; snprintf(path, sizeof(path), "/proc/%d/status", pid_); - FileReader status_file; - if (!status_file.Open(base::FilePath(path))) { + std::string contents; + if (!connection->ReadFileContents(base::FilePath(path), &contents)) { return false; } + StringFile status_file; + status_file.SetString(contents); DelimitedFileReader status_file_line_reader(&status_file); @@ -230,10 +236,16 @@ bool ProcessInfo::StartTime(timeval* start_time) const { if (start_time_initialized_.is_uninitialized()) { start_time_initialized_.set_invalid(); ProcStatReader reader; - if (!reader.Initialize(pid_)) { + if (!reader.Initialize(connection_, pid_)) { return false; } - if (!reader.StartTime(&start_time_)) { + timespec boot_time_ts; + if (!GetBootTime(&boot_time_ts)) { + return false; + } + timeval boot_time; + TimespecToTimeval(boot_time_ts, &boot_time); + if (!reader.StartTime(boot_time, &start_time_)) { return false; } start_time_initialized_.set_valid(); @@ -252,10 +264,12 @@ bool ProcessInfo::Arguments(std::vector* argv) const { char path[32]; snprintf(path, sizeof(path), "/proc/%d/cmdline", pid_); - FileReader cmdline_file; - if (!cmdline_file.Open(base::FilePath(path))) { + std::string contents; + if (!connection_->ReadFileContents(base::FilePath(path), &contents)) { return false; } + StringFile cmdline_file; + cmdline_file.SetString(contents); DelimitedFileReader cmdline_file_field_reader(&cmdline_file); diff --git a/util/posix/process_info_mac.cc b/util/posix/process_info_mac.cc index fe9fb654..9e86e084 100644 --- a/util/posix/process_info_mac.cc +++ b/util/posix/process_info_mac.cc @@ -18,6 +18,7 @@ #include "base/logging.h" #include "base/mac/mach_logging.h" +#include "base/stl_util.h" namespace crashpad { @@ -32,7 +33,7 @@ bool ProcessInfo::InitializeWithPid(pid_t pid) { int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid}; size_t len = sizeof(kern_proc_info_); - if (sysctl(mib, arraysize(mib), &kern_proc_info_, &len, nullptr, 0) != 0) { + if (sysctl(mib, base::size(mib), &kern_proc_info_, &len, nullptr, 0) != 0) { PLOG(ERROR) << "sysctl for pid " << pid; return false; } @@ -111,7 +112,7 @@ std::set ProcessInfo::SupplementaryGroups() const { const short ngroups = kern_proc_info_.kp_eproc.e_ucred.cr_ngroups; DCHECK_GE(ngroups, 0); DCHECK_LE(static_cast(ngroups), - arraysize(kern_proc_info_.kp_eproc.e_ucred.cr_groups)); + base::size(kern_proc_info_.kp_eproc.e_ucred.cr_groups)); const gid_t* groups = kern_proc_info_.kp_eproc.e_ucred.cr_groups; return std::set(&groups[0], &groups[ngroups]); @@ -168,7 +169,7 @@ bool ProcessInfo::Arguments(std::vector* argv) const { do { int mib[] = {CTL_KERN, KERN_PROCARGS2, pid}; int rv = - sysctl(mib, arraysize(mib), nullptr, &args_size_estimate, nullptr, 0); + sysctl(mib, base::size(mib), nullptr, &args_size_estimate, nullptr, 0); if (rv != 0) { PLOG(ERROR) << "sysctl (size) for pid " << pid; return false; @@ -176,7 +177,7 @@ bool ProcessInfo::Arguments(std::vector* argv) const { args_size = args_size_estimate + 1; args.resize(args_size); - rv = sysctl(mib, arraysize(mib), &args[0], &args_size, nullptr, 0); + rv = sysctl(mib, base::size(mib), &args[0], &args_size, nullptr, 0); if (rv != 0) { PLOG(ERROR) << "sysctl (data) for pid " << pid; return false; diff --git a/util/posix/process_info_test.cc b/util/posix/process_info_test.cc index 6154ef70..69390fae 100644 --- a/util/posix/process_info_test.cc +++ b/util/posix/process_info_test.cc @@ -14,6 +14,7 @@ #include "util/posix/process_info.h" +#include #include #include @@ -21,6 +22,7 @@ #include #include +#include "base/strings/string_number_conversions.h" #include "base/strings/stringprintf.h" #include "build/build_config.h" #include "gtest/gtest.h" @@ -29,6 +31,7 @@ #include "test/multiprocess.h" #include "util/file/file_io.h" #include "util/misc/implicit_cast.h" +#include "util/string/split_string.h" #if defined(OS_LINUX) || defined(OS_ANDROID) #include "util/linux/direct_ptrace_connection.h" @@ -93,11 +96,39 @@ void TestProcessSelfOrClone(const ProcessInfo& process_info) { time(&now); EXPECT_LE(start_time.tv_sec, now); + const std::vector& expect_argv = GetMainArguments(); + +#if defined(OS_ANDROID) || defined(OS_LINUX) + // Prior to Linux 4.2, the kernel only allowed reading a single page from + // /proc//cmdline, causing any further arguments to be truncated. Disable + // testing arguments in this case. + // TODO(jperaza): The main arguments are stored on the main thread's stack + // (and so should be included in dumps automatically), and + // ProcessInfo::Arguments() might be updated to read the arguments directly, + // rather than via procfs on older kernels. + utsname uts; + ASSERT_EQ(uname(&uts), 0) << ErrnoMessage("uname"); + std::vector parts = SplitString(uts.release, '.'); + ASSERT_GE(parts.size(), 2u); + + int major, minor; + ASSERT_TRUE(base::StringToInt(parts[0], &major)); + ASSERT_TRUE(base::StringToInt(parts[1], &minor)); + + size_t argv_size = 0; + for (const auto& arg : expect_argv) { + argv_size += arg.size() + 1; + } + + if ((major < 4 || (major == 4 && minor < 2)) && + argv_size > static_cast(getpagesize())) { + return; + } +#endif // OS_ANDROID || OS_LINUX + std::vector argv; ASSERT_TRUE(process_info.Arguments(&argv)); - const std::vector& expect_argv = GetMainArguments(); - // expect_argv always contains the initial view of the arguments at the time // the program was invoked. argv may contain this view, or it may contain the // current view of arguments after gtest argv processing. argv may be a subset diff --git a/util/posix/scoped_mmap.cc b/util/posix/scoped_mmap.cc index 4860cecb..fbf47fae 100644 --- a/util/posix/scoped_mmap.cc +++ b/util/posix/scoped_mmap.cc @@ -19,6 +19,7 @@ #include #include "base/logging.h" +#include "base/numerics/safe_conversions.h" #include "base/numerics/safe_math.h" namespace { @@ -32,6 +33,11 @@ bool Munmap(uintptr_t addr, size_t len) { return true; } +size_t RoundPage(size_t size) { + const size_t kPageMask = base::checked_cast(getpagesize()) - 1; + return (size + kPageMask) & ~kPageMask; +} + } // namespace namespace crashpad { @@ -40,7 +46,7 @@ ScopedMmap::ScopedMmap() {} ScopedMmap::~ScopedMmap() { if (is_valid()) { - Munmap(reinterpret_cast(addr_), len_); + Munmap(reinterpret_cast(addr_), RoundPage(len_)); } } @@ -50,26 +56,28 @@ bool ScopedMmap::Reset() { bool ScopedMmap::ResetAddrLen(void* addr, size_t len) { const uintptr_t new_addr = reinterpret_cast(addr); + const size_t new_len_round = RoundPage(len); if (addr == MAP_FAILED) { DCHECK_EQ(len, 0u); } else { DCHECK_NE(len, 0u); DCHECK_EQ(new_addr % getpagesize(), 0u); - DCHECK_EQ(len % getpagesize(), 0u); - DCHECK((base::CheckedNumeric(new_addr) + (len - 1)).IsValid()); + DCHECK((base::CheckedNumeric(new_addr) + (new_len_round - 1)) + .IsValid()); } bool result = true; if (is_valid()) { const uintptr_t old_addr = reinterpret_cast(addr_); + const size_t old_len_round = RoundPage(len_); if (old_addr < new_addr) { - result &= Munmap(old_addr, std::min(len_, new_addr - old_addr)); + result &= Munmap(old_addr, std::min(old_len_round, new_addr - old_addr)); } - if (old_addr + len_ > new_addr + len) { - uintptr_t unmap_start = std::max(old_addr, new_addr + len); - result &= Munmap(unmap_start, old_addr + len_ - unmap_start); + if (old_addr + old_len_round > new_addr + new_len_round) { + uintptr_t unmap_start = std::max(old_addr, new_addr + new_len_round); + result &= Munmap(unmap_start, old_addr + old_len_round - unmap_start); } } @@ -104,7 +112,7 @@ bool ScopedMmap::ResetMmap(void* addr, } bool ScopedMmap::Mprotect(int prot) { - if (mprotect(addr_, len_, prot) < 0) { + if (mprotect(addr_, RoundPage(len_), prot) < 0) { PLOG(ERROR) << "mprotect"; return false; } diff --git a/util/posix/scoped_mmap.h b/util/posix/scoped_mmap.h index b0ff3dc0..9f22372b 100644 --- a/util/posix/scoped_mmap.h +++ b/util/posix/scoped_mmap.h @@ -91,6 +91,11 @@ class ScopedMmap { } //! \brief Returns the size of the memory-mapped region. + //! + //! This is the value originally passed to ResetAddrLen() or ResetMmap(), or + //! after Reset(), `0`. It may not be a round number of pages. Providing the + //! passed-in value is intended to ease tracking the intended lengths of + //! memory-mapped regions backed by files whose sizes are not whole pages. size_t len() const { return len_; } private: diff --git a/util/posix/scoped_mmap_test.cc b/util/posix/scoped_mmap_test.cc index 7312b849..5279fdb8 100644 --- a/util/posix/scoped_mmap_test.cc +++ b/util/posix/scoped_mmap_test.cc @@ -20,6 +20,7 @@ #include "base/numerics/safe_conversions.h" #include "base/rand_util.h" +#include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "gtest/gtest.h" #include "test/gtest_death.h" @@ -150,7 +151,7 @@ TEST(ScopedMmapDeathTest, ResetAddrLen_Shrink) { EXPECT_EQ(mapping.len(), 3 * kPageSize); TestCookie cookies[3]; - for (size_t index = 0; index < arraysize(cookies); ++index) { + for (size_t index = 0; index < base::size(cookies); ++index) { cookies[index].SetUp(reinterpret_cast( mapping.addr_as() + index * kPageSize)); } @@ -185,7 +186,7 @@ TEST(ScopedMmap, ResetAddrLen_Grow) { EXPECT_EQ(mapping.len(), kPageSize); TestCookie cookies[3]; - for (size_t index = 0; index < arraysize(cookies); ++index) { + for (size_t index = 0; index < base::size(cookies); ++index) { cookies[index].SetUp(reinterpret_cast( reinterpret_cast(pages) + index * kPageSize)); } @@ -196,7 +197,7 @@ TEST(ScopedMmap, ResetAddrLen_Grow) { EXPECT_EQ(mapping.addr(), pages); EXPECT_EQ(mapping.len(), 3 * kPageSize); - for (size_t index = 0; index < arraysize(cookies); ++index) { + for (size_t index = 0; index < base::size(cookies); ++index) { SCOPED_TRACE(base::StringPrintf("index %zu", index)); EXPECT_EQ(cookies[index].Observed(), cookies[index].Expected()); } @@ -217,7 +218,7 @@ TEST(ScopedMmapDeathTest, ResetAddrLen_MoveDownAndGrow) { EXPECT_EQ(mapping.len(), kPageSize); TestCookie cookies[3]; - for (size_t index = 0; index < arraysize(cookies); ++index) { + for (size_t index = 0; index < base::size(cookies); ++index) { cookies[index].SetUp(reinterpret_cast( reinterpret_cast(pages) + index * kPageSize)); } @@ -248,7 +249,7 @@ TEST(ScopedMmapDeathTest, ResetAddrLen_MoveUpAndShrink) { EXPECT_EQ(mapping.len(), 2 * kPageSize); TestCookie cookies[3]; - for (size_t index = 0; index < arraysize(cookies); ++index) { + for (size_t index = 0; index < base::size(cookies); ++index) { cookies[index].SetUp(reinterpret_cast( reinterpret_cast(pages) + index * kPageSize)); } @@ -293,6 +294,94 @@ TEST(ScopedMmapDeathTest, ResetMmap) { EXPECT_DEATH_CRASH(cookie.Check(), ""); } +TEST(ScopedMmapDeathTest, NotIntegralNumberOfPages) { + ScopedMmap mapping; + EXPECT_FALSE(mapping.is_valid()); + EXPECT_EQ(mapping.addr(), MAP_FAILED); + EXPECT_EQ(mapping.len(), 0u); + + ASSERT_TRUE(mapping.Reset()); + EXPECT_FALSE(mapping.is_valid()); + + // Establishing a half-page mapping actually establishes a single page. + const size_t kPageSize = base::checked_cast(getpagesize()); + const size_t kHalfPageSize = kPageSize / 2; + ASSERT_TRUE(ScopedMmapResetMmap(&mapping, kHalfPageSize)); + EXPECT_TRUE(mapping.is_valid()); + EXPECT_NE(mapping.addr(), MAP_FAILED); + EXPECT_EQ(mapping.len(), kHalfPageSize); + + TestCookie cookie; + cookie.SetUp(mapping.addr_as()); + + // Shrinking a one-page mapping to a half page is a no-op. + void* orig_addr = mapping.addr(); + ASSERT_TRUE(mapping.ResetAddrLen(orig_addr, kHalfPageSize)); + EXPECT_TRUE(mapping.is_valid()); + EXPECT_EQ(mapping.addr(), orig_addr); + EXPECT_EQ(mapping.len(), kHalfPageSize); + + EXPECT_EQ(cookie.Observed(), cookie.Expected()); + + // Same thing shrinking it to a single byte, or one byte less than a whole + // page. + ASSERT_TRUE(mapping.ResetAddrLen(orig_addr, 1)); + EXPECT_TRUE(mapping.is_valid()); + EXPECT_EQ(mapping.addr(), orig_addr); + EXPECT_EQ(mapping.len(), 1u); + + EXPECT_EQ(cookie.Observed(), cookie.Expected()); + + ASSERT_TRUE(mapping.ResetAddrLen(orig_addr, kPageSize - 1)); + EXPECT_TRUE(mapping.is_valid()); + EXPECT_EQ(mapping.addr(), orig_addr); + EXPECT_EQ(mapping.len(), kPageSize - 1); + + EXPECT_EQ(cookie.Observed(), cookie.Expected()); + + // Shrinking a two-page mapping to a half page frees the second page but + // leaves the first alone. + ASSERT_TRUE(ScopedMmapResetMmap(&mapping, 2 * kPageSize)); + EXPECT_TRUE(mapping.is_valid()); + EXPECT_NE(mapping.addr(), MAP_FAILED); + EXPECT_EQ(mapping.len(), 2 * kPageSize); + + TestCookie two_cookies[2]; + for (size_t index = 0; index < base::size(two_cookies); ++index) { + two_cookies[index].SetUp(reinterpret_cast( + mapping.addr_as() + index * kPageSize)); + } + + orig_addr = mapping.addr(); + ASSERT_TRUE(mapping.ResetAddrLen(orig_addr, kHalfPageSize)); + EXPECT_TRUE(mapping.is_valid()); + EXPECT_EQ(mapping.addr(), orig_addr); + EXPECT_EQ(mapping.len(), kHalfPageSize); + + EXPECT_EQ(two_cookies[0].Observed(), two_cookies[0].Expected()); + EXPECT_DEATH_CRASH(two_cookies[1].Check(), ""); + + // Shrinking a two-page mapping to a page and a half is a no-op. + ASSERT_TRUE(ScopedMmapResetMmap(&mapping, 2 * kPageSize)); + EXPECT_TRUE(mapping.is_valid()); + EXPECT_NE(mapping.addr(), MAP_FAILED); + EXPECT_EQ(mapping.len(), 2 * kPageSize); + + for (size_t index = 0; index < base::size(two_cookies); ++index) { + two_cookies[index].SetUp(reinterpret_cast( + mapping.addr_as() + index * kPageSize)); + } + + orig_addr = mapping.addr(); + ASSERT_TRUE(mapping.ResetAddrLen(orig_addr, kPageSize + kHalfPageSize)); + EXPECT_TRUE(mapping.is_valid()); + EXPECT_EQ(mapping.addr(), orig_addr); + EXPECT_EQ(mapping.len(), kPageSize + kHalfPageSize); + + EXPECT_EQ(two_cookies[0].Observed(), two_cookies[0].Expected()); + EXPECT_EQ(two_cookies[1].Observed(), two_cookies[1].Expected()); +} + TEST(ScopedMmapDeathTest, Mprotect) { ScopedMmap mapping; diff --git a/util/posix/signals.cc b/util/posix/signals.cc index 63764ab8..53c10389 100644 --- a/util/posix/signals.cc +++ b/util/posix/signals.cc @@ -19,6 +19,7 @@ #include #include "base/logging.h" +#include "base/stl_util.h" namespace crashpad { @@ -92,9 +93,14 @@ constexpr int kTerminateSignals[] = { bool InstallHandlers(const std::vector& signals, Signals::Handler handler, int flags, - Signals::OldActions* old_actions) { + Signals::OldActions* old_actions, + const std::set* unhandled_signals) { bool success = true; for (int sig : signals) { + if (unhandled_signals && + unhandled_signals->find(sig) != unhandled_signals->end()) { + continue; + } success &= Signals::InstallHandler( sig, handler, @@ -118,7 +124,7 @@ bool IsSignalInSet(int sig, const int* set, size_t set_size) { struct sigaction* Signals::OldActions::ActionForSignal(int sig) { DCHECK_GT(sig, 0); const size_t slot = sig - 1; - DCHECK_LT(slot, arraysize(actions_)); + DCHECK_LT(slot, base::size(actions_)); return &actions_[slot]; } @@ -150,12 +156,15 @@ bool Signals::InstallDefaultHandler(int sig) { // static bool Signals::InstallCrashHandlers(Handler handler, int flags, - OldActions* old_actions) { + OldActions* old_actions, + const std::set* unhandled_signals) { return InstallHandlers( - std::vector(kCrashSignals, kCrashSignals + arraysize(kCrashSignals)), + std::vector(kCrashSignals, + kCrashSignals + base::size(kCrashSignals)), handler, flags, - old_actions); + old_actions, + unhandled_signals); } // static @@ -164,10 +173,11 @@ bool Signals::InstallTerminateHandlers(Handler handler, OldActions* old_actions) { return InstallHandlers( std::vector(kTerminateSignals, - kTerminateSignals + arraysize(kTerminateSignals)), + kTerminateSignals + base::size(kTerminateSignals)), handler, flags, - old_actions); + old_actions, + nullptr); } // static @@ -279,12 +289,12 @@ void Signals::RestoreHandlerAndReraiseSignalOnReturn( // static bool Signals::IsCrashSignal(int sig) { - return IsSignalInSet(sig, kCrashSignals, arraysize(kCrashSignals)); + return IsSignalInSet(sig, kCrashSignals, base::size(kCrashSignals)); } // static bool Signals::IsTerminateSignal(int sig) { - return IsSignalInSet(sig, kTerminateSignals, arraysize(kTerminateSignals)); + return IsSignalInSet(sig, kTerminateSignals, base::size(kTerminateSignals)); } } // namespace crashpad diff --git a/util/posix/signals.h b/util/posix/signals.h index dc550594..368161bf 100644 --- a/util/posix/signals.h +++ b/util/posix/signals.h @@ -17,6 +17,8 @@ #include +#include + #include "base/macros.h" namespace crashpad { @@ -114,15 +116,19 @@ class Signals { //! the new action. May be `nullptr` if not needed. The same \a //! old_actions object may be used for calls to both this function and //! InstallTerminateHandlers(). + //! \param[in] unhandled_signals Signal handlers will not be installed for + //! signal numbers in this set. Optional. //! //! \return `true` on success. `false` on failure with a message logged. //! //! \warning This function may not be called from a signal handler because of //! its use of logging. See RestoreHandlerAndReraiseSignalOnReturn() //! instead. - static bool InstallCrashHandlers(Handler handler, - int flags, - OldActions* old_actions); + static bool InstallCrashHandlers( + Handler handler, + int flags, + OldActions* old_actions, + const std::set* unhandled_signals = nullptr); //! \brief Installs a new signal handler for all signals associated with //! termination. diff --git a/util/posix/signals_test.cc b/util/posix/signals_test.cc index 75ff5580..58bfa8f8 100644 --- a/util/posix/signals_test.cc +++ b/util/posix/signals_test.cc @@ -26,6 +26,7 @@ #include "base/compiler_specific.h" #include "base/files/scoped_file.h" #include "base/logging.h" +#include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "build/build_config.h" #include "gtest/gtest.h" @@ -257,7 +258,12 @@ class SignalsTest : public Multiprocess { void MultiprocessChild() override { bool (*install_handlers)(Signals::Handler, int, Signals::OldActions*); if (Signals::IsCrashSignal(sig_)) { - install_handlers = Signals::InstallCrashHandlers; + install_handlers = [](Signals::Handler handler, + int flags, + Signals::OldActions* old_actions) { + return Signals::InstallCrashHandlers( + handler, flags, old_actions, nullptr); + }; } else if (Signals::IsTerminateSignal(sig_)) { install_handlers = Signals::InstallTerminateHandlers; } else { @@ -340,7 +346,7 @@ TEST(Signals, WillSignalReraiseAutonomously) { {SIGHUP, SEGV_MAPERR, false}, {SIGINT, SI_USER, false}, }; - for (size_t index = 0; index < arraysize(kTestData); ++index) { + for (size_t index = 0; index < base::size(kTestData); ++index) { const auto test_data = kTestData[index]; SCOPED_TRACE(base::StringPrintf( "index %zu, sig %d, code %d", index, test_data.sig, test_data.code)); diff --git a/util/posix/symbolic_constants_posix.cc b/util/posix/symbolic_constants_posix.cc index 8008ffb6..5937c576 100644 --- a/util/posix/symbolic_constants_posix.cc +++ b/util/posix/symbolic_constants_posix.cc @@ -18,7 +18,7 @@ #include #include -#include "base/macros.h" +#include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "build/build_config.h" #include "util/misc/implicit_cast.h" @@ -137,9 +137,9 @@ constexpr const char* kSignalNames[] = { }; #if defined(OS_LINUX) || defined(OS_ANDROID) // NSIG is 64 to account for real-time signals. -static_assert(arraysize(kSignalNames) == 32, "kSignalNames length"); +static_assert(base::size(kSignalNames) == 32, "kSignalNames length"); #else -static_assert(arraysize(kSignalNames) == NSIG, "kSignalNames length"); +static_assert(base::size(kSignalNames) == NSIG, "kSignalNames length"); #endif constexpr char kSigPrefix[] = "SIG"; @@ -151,7 +151,7 @@ namespace crashpad { std::string SignalToString(int signal, SymbolicConstantToStringOptions options) { const char* signal_name = - implicit_cast(signal) < arraysize(kSignalNames) + implicit_cast(signal) < base::size(kSignalNames) ? kSignalNames[signal] : nullptr; if (!signal_name) { @@ -176,8 +176,7 @@ bool StringToSignal(const base::StringPiece& string, string.substr(0, strlen(kSigPrefix)).compare(kSigPrefix) == 0; base::StringPiece short_string = can_match_full ? string.substr(strlen(kSigPrefix)) : string; - for (int index = 0; - index < implicit_cast(arraysize(kSignalNames)); + for (int index = 0; index < implicit_cast(base::size(kSignalNames)); ++index) { const char* signal_name = kSignalNames[index]; if (!signal_name) { diff --git a/util/posix/symbolic_constants_posix_test.cc b/util/posix/symbolic_constants_posix_test.cc index 32c1d434..c7d62ee5 100644 --- a/util/posix/symbolic_constants_posix_test.cc +++ b/util/posix/symbolic_constants_posix_test.cc @@ -17,13 +17,14 @@ #include #include -#include "base/macros.h" +#include "base/stl_util.h" #include "base/strings/string_piece.h" #include "base/strings/stringprintf.h" #include "build/build_config.h" #include "gtest/gtest.h" -#define NUL_TEST_DATA(string) { string, arraysize(string) - 1 } +#define NUL_TEST_DATA(string) \ + { string, base::size(string) - 1 } namespace crashpad { namespace test { @@ -115,7 +116,7 @@ void TestSignalToString(int value, } TEST(SymbolicConstantsPOSIX, SignalToString) { - for (size_t index = 0; index < arraysize(kSignalTestData); ++index) { + for (size_t index = 0; index < base::size(kSignalTestData); ++index) { SCOPED_TRACE(base::StringPrintf("index %zu", index)); TestSignalToString(kSignalTestData[index].signal, kSignalTestData[index].full_name, @@ -170,12 +171,11 @@ TEST(SymbolicConstantsPOSIX, StringToSignal) { kAllowFullName | kAllowShortName | kAllowNumber, }; - for (size_t option_index = 0; - option_index < arraysize(kOptions); + for (size_t option_index = 0; option_index < base::size(kOptions); ++option_index) { SCOPED_TRACE(base::StringPrintf("option_index %zu", option_index)); StringToSymbolicConstantOptions options = kOptions[option_index]; - for (size_t index = 0; index < arraysize(kSignalTestData); ++index) { + for (size_t index = 0; index < base::size(kSignalTestData); ++index) { SCOPED_TRACE(base::StringPrintf("index %zu", index)); int signal = kSignalTestData[index].signal; { @@ -213,7 +213,7 @@ TEST(SymbolicConstantsPOSIX, StringToSignal) { "", }; - for (size_t index = 0; index < arraysize(kNegativeTestData); ++index) { + for (size_t index = 0; index < base::size(kNegativeTestData); ++index) { SCOPED_TRACE(base::StringPrintf("index %zu", index)); TestStringToSignal(kNegativeTestData[index], options, false, 0); } @@ -234,7 +234,7 @@ TEST(SymbolicConstantsPOSIX, StringToSignal) { NUL_TEST_DATA("1\0002"), }; - for (size_t index = 0; index < arraysize(kNULTestData); ++index) { + for (size_t index = 0; index < base::size(kNULTestData); ++index) { SCOPED_TRACE(base::StringPrintf("index %zu", index)); base::StringPiece string(kNULTestData[index].string, kNULTestData[index].length); diff --git a/util/process/process_id.h b/util/process/process_id.h new file mode 100644 index 00000000..113f6fc6 --- /dev/null +++ b/util/process/process_id.h @@ -0,0 +1,54 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_PROCESS_PROCESS_ID_H_ +#define CRASHPAD_UTIL_PROCESS_PROCESS_ID_H_ + +#include + +#include "base/format_macros.h" +#include "build/build_config.h" + +#if defined(OS_POSIX) +#include +#elif defined(OS_WIN) +#include +#elif defined(OS_FUCHSIA) +#include +#endif + +namespace crashpad { + +#if defined(OS_POSIX) || DOXYGEN +//! \brief Alias for platform-specific type to represent a process. +using ProcessID = pid_t; +constexpr ProcessID kInvalidProcessID = -1; +static_assert(std::is_same::value, "Port."); +#define PRI_PROCESS_ID "d" +#elif defined(OS_WIN) +using ProcessID = DWORD; +constexpr ProcessID kInvalidProcessID = 0; +#define PRI_PROCESS_ID "lu" +#elif defined(OS_FUCHSIA) +using ProcessID = zx_koid_t; +constexpr ProcessID kInvalidProcessID = ZX_KOID_INVALID; +static_assert(std::is_same::value, "Port."); +#define PRI_PROCESS_ID PRId64 +#else +#error Port. +#endif + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_PROCESS_PROCESS_ID_H_ diff --git a/util/process/process_memory.cc b/util/process/process_memory.cc index 6bc00101..ab87b940 100644 --- a/util/process/process_memory.cc +++ b/util/process/process_memory.cc @@ -14,14 +14,23 @@ #include "util/process/process_memory.h" +#include + #include "base/logging.h" +#include "util/numeric/safe_assignment.h" namespace crashpad { -bool ProcessMemory::Read(VMAddress address, size_t size, void* buffer) const { +bool ProcessMemory::Read(VMAddress address, VMSize size, void* buffer) const { + size_t local_size; + if (!AssignIfInRange(&local_size, size)) { + LOG(ERROR) << "size " << size << " out of bounds for size_t"; + return false; + } + char* buffer_c = static_cast(buffer); - while (size > 0) { - ssize_t bytes_read = ReadUpTo(address, size, buffer_c); + while (local_size > 0) { + ssize_t bytes_read = ReadUpTo(address, local_size, buffer_c); if (bytes_read < 0) { return false; } @@ -29,8 +38,8 @@ bool ProcessMemory::Read(VMAddress address, size_t size, void* buffer) const { LOG(ERROR) << "short read"; return false; } - DCHECK_LE(static_cast(bytes_read), size); - size -= bytes_read; + DCHECK_LE(static_cast(bytes_read), local_size); + local_size -= bytes_read; address += bytes_read; buffer_c += bytes_read; } @@ -39,15 +48,21 @@ bool ProcessMemory::Read(VMAddress address, size_t size, void* buffer) const { bool ProcessMemory::ReadCStringInternal(VMAddress address, bool has_size, - size_t size, + VMSize size, std::string* string) const { + size_t local_size; + if (!AssignIfInRange(&local_size, size)) { + LOG(ERROR) << "size " << size << " out of bounds for size_t"; + return false; + } + string->clear(); char buffer[4096]; do { size_t read_size; if (has_size) { - read_size = std::min(sizeof(buffer), size); + read_size = std::min(sizeof(buffer), local_size); } else { read_size = sizeof(buffer); } @@ -68,8 +83,8 @@ bool ProcessMemory::ReadCStringInternal(VMAddress address, string->append(buffer, bytes_read); address += bytes_read; - size -= bytes_read; - } while (!has_size || size > 0); + local_size -= bytes_read; + } while (!has_size || local_size > 0); LOG(ERROR) << "unterminated string"; return false; diff --git a/util/process/process_memory.h b/util/process/process_memory.h index d67c3828..eeb78e97 100644 --- a/util/process/process_memory.h +++ b/util/process/process_memory.h @@ -19,8 +19,14 @@ #include +#include "build/build_config.h" #include "util/misc/address_types.h" +#if defined(OS_WIN) +#include +typedef SSIZE_T ssize_t; +#endif // defined(OS_WIN) + namespace crashpad { //! \brief Abstract base class for accessing the memory of another process. @@ -40,7 +46,7 @@ class ProcessMemory { //! //! \return `true` on success, with \a buffer filled appropriately. `false` on //! failure, with a message logged. - bool Read(VMAddress address, size_t size, void* buffer) const; + bool Read(VMAddress address, VMSize size, void* buffer) const; //! \brief Reads a `NUL`-terminated C string from the target process into a //! string in the current process. @@ -73,7 +79,7 @@ class ProcessMemory { //! a `NUL` terminator is not found within \a size bytes, or when //! encountering unmapped or unreadable pages. bool ReadCStringSizeLimited(VMAddress address, - size_t size, + VMSize size, std::string* string) const { return ReadCStringInternal(address, true, size, string); } @@ -118,8 +124,11 @@ class ProcessMemory { //! encountering unmapped or unreadable pages. virtual bool ReadCStringInternal(VMAddress address, bool has_size, - size_t size, + VMSize size, std::string* string) const; + + // Allow ProcessMemorySanitized to call ReadUpTo. + friend class ProcessMemorySanitized; }; } // namespace crashpad diff --git a/util/process/process_memory_fuchsia.cc b/util/process/process_memory_fuchsia.cc index 212e1c6f..b9c4a0c3 100644 --- a/util/process/process_memory_fuchsia.cc +++ b/util/process/process_memory_fuchsia.cc @@ -14,8 +14,6 @@ #include "util/process/process_memory_fuchsia.h" -#include - #include #include "base/logging.h" @@ -28,9 +26,13 @@ ProcessMemoryFuchsia::ProcessMemoryFuchsia() ProcessMemoryFuchsia::~ProcessMemoryFuchsia() {} -bool ProcessMemoryFuchsia::Initialize(zx_handle_t process) { +bool ProcessMemoryFuchsia::Initialize(const zx::unowned_process& process) { + return Initialize(*process); +} + +bool ProcessMemoryFuchsia::Initialize(const zx::process& process) { INITIALIZATION_STATE_SET_INITIALIZING(initialized_); - process_ = process; + process_ = zx::unowned_process(process); INITIALIZATION_STATE_SET_VALID(initialized_); return true; } @@ -42,8 +44,7 @@ ssize_t ProcessMemoryFuchsia::ReadUpTo(VMAddress address, 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); + zx_status_t status = process_->read_memory(address, buffer, size, &actual); if (status != ZX_OK) { ZX_LOG(ERROR, status) << "zx_process_read_memory"; diff --git a/util/process/process_memory_fuchsia.h b/util/process/process_memory_fuchsia.h index e6cdd3e8..6c9cebaa 100644 --- a/util/process/process_memory_fuchsia.h +++ b/util/process/process_memory_fuchsia.h @@ -15,7 +15,7 @@ #ifndef CRASHPAD_UTIL_PROCESS_PROCESS_MEMORY_FUCHSIA_H_ #define CRASHPAD_UTIL_PROCESS_PROCESS_MEMORY_FUCHSIA_H_ -#include +#include #include @@ -40,12 +40,15 @@ class ProcessMemoryFuchsia final : public ProcessMemory { //! \param[in] process The handle to the target process. //! //! \return `true` on success, `false` on failure with a message logged. - bool Initialize(zx_handle_t process); + bool Initialize(const zx::process& process); + // TODO(wez): Remove this overload when zx::unowned_process allows implicit + // copy. + bool Initialize(const zx::unowned_process& process); private: ssize_t ReadUpTo(VMAddress address, size_t size, void* buffer) const override; - zx_handle_t process_; + zx::unowned_process process_; InitializationStateDcheck initialized_; DISALLOW_COPY_AND_ASSIGN(ProcessMemoryFuchsia); diff --git a/util/mach/task_memory.cc b/util/process/process_memory_mac.cc similarity index 52% rename from util/mach/task_memory.cc rename to util/process/process_memory_mac.cc index 6168434d..29357f3d 100644 --- a/util/mach/task_memory.cc +++ b/util/process/process_memory_mac.cc @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "util/mach/task_memory.h" +#include "util/process/process_memory_mac.h" #include #include @@ -26,11 +26,10 @@ namespace crashpad { -TaskMemory::MappedMemory::~MappedMemory() { -} +ProcessMemoryMac::MappedMemory::~MappedMemory() {} -bool TaskMemory::MappedMemory::ReadCString( - size_t offset, std::string* string) const { +bool ProcessMemoryMac::MappedMemory::ReadCString(size_t offset, + std::string* string) const { if (offset >= user_size_) { LOG(WARNING) << "offset out of range"; return false; @@ -48,10 +47,10 @@ bool TaskMemory::MappedMemory::ReadCString( return true; } -TaskMemory::MappedMemory::MappedMemory(vm_address_t vm_address, - size_t vm_size, - size_t user_offset, - size_t user_size) +ProcessMemoryMac::MappedMemory::MappedMemory(vm_address_t vm_address, + size_t vm_size, + size_t user_offset, + size_t user_size) : vm_(vm_address, vm_size), data_(reinterpret_cast(vm_address + user_offset)), user_size_(user_size) { @@ -64,22 +63,20 @@ TaskMemory::MappedMemory::MappedMemory(vm_address_t vm_address, DCHECK_LE(user_end, vm_end); } -TaskMemory::TaskMemory(task_t task) : task_(task) { -} +ProcessMemoryMac::ProcessMemoryMac() : task_(TASK_NULL), initialized_() {} -bool TaskMemory::Read(mach_vm_address_t address, size_t size, void* buffer) { - std::unique_ptr memory = ReadMapped(address, size); - if (!memory) { - return false; - } - - memcpy(buffer, memory->data(), size); +bool ProcessMemoryMac::Initialize(task_t task) { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + task_ = task; + INITIALIZATION_STATE_SET_VALID(initialized_); return true; } -std::unique_ptr TaskMemory::ReadMapped( +std::unique_ptr ProcessMemoryMac::ReadMapped( mach_vm_address_t address, - size_t size) { + size_t size) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + if (size == 0) { return std::unique_ptr(new MappedMemory(0, 0, 0, 0)); } @@ -97,63 +94,43 @@ std::unique_ptr TaskMemory::ReadMapped( "mach_vm_read(0x%llx, 0x%llx)", region_address, region_size); return std::unique_ptr(); } + if (region_count != region_size) { + LOG(ERROR) << base::StringPrintf( + "mach_vm_read() unexpected read: 0x%x != 0x%llx bytes", + region_count, + region_size); + if (region_count) + vm_deallocate(mach_task_self(), region, region_count); + return std::unique_ptr(); + } - DCHECK_EQ(region_count, region_size); return std::unique_ptr( new MappedMemory(region, region_size, address - region_address, size)); } -bool TaskMemory::ReadCString(mach_vm_address_t address, std::string* string) { - return ReadCStringInternal(address, false, 0, string); -} +ssize_t ProcessMemoryMac::ReadUpTo(VMAddress address, + size_t size, + void* buffer) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + DCHECK_LE(size, (size_t)std::numeric_limits::max()); -bool TaskMemory::ReadCStringSizeLimited(mach_vm_address_t address, - mach_vm_size_t size, - std::string* string) { - return ReadCStringInternal(address, true, size, string); -} + std::unique_ptr memory = ReadMapped(address, size); + if (!memory) { + // If we can not read the entire mapping, try to perform a short read of the + // first page instead. This is necessary to support ReadCString(). + size_t short_read = PAGE_SIZE - (address % PAGE_SIZE); + if (short_read >= size) + return -1; -bool TaskMemory::ReadCStringInternal(mach_vm_address_t address, - bool has_size, - mach_vm_size_t size, - std::string* string) { - if (has_size) { - if (size == 0) { - string->clear(); - return true; - } - } else { - size = PAGE_SIZE; + memory = ReadMapped(address, short_read); + if (!memory) + return -1; + + size = short_read; } - std::string local_string; - mach_vm_address_t read_address = address; - do { - mach_vm_size_t read_length = - std::min(size, PAGE_SIZE - (read_address % PAGE_SIZE)); - std::unique_ptr read_region = - ReadMapped(read_address, read_length); - if (!read_region) { - return false; - } - - const char* read_region_data = - reinterpret_cast(read_region->data()); - size_t read_region_data_length = strnlen(read_region_data, read_length); - local_string.append(read_region_data, read_region_data_length); - if (read_region_data_length < read_length) { - string->swap(local_string); - return true; - } - - if (has_size) { - size -= read_length; - } - read_address = mach_vm_trunc_page(read_address + read_length); - } while ((!has_size || size > 0) && read_address > address); - - LOG(WARNING) << base::StringPrintf("unterminated string at 0x%llx", address); - return false; + memcpy(buffer, memory->data(), size); + return static_cast(size); } } // namespace crashpad diff --git a/util/mach/task_memory.h b/util/process/process_memory_mac.h similarity index 57% rename from util/mach/task_memory.h rename to util/process/process_memory_mac.h index c8caf85c..214e4890 100644 --- a/util/mach/task_memory.h +++ b/util/process/process_memory_mac.h @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef CRASHPAD_UTIL_MACH_TASK_MEMORY_H_ -#define CRASHPAD_UTIL_MACH_TASK_MEMORY_H_ +#ifndef CRASHPAD_UTIL_PROCESS_PROCESS_MEMORY_MAC_H_ +#define CRASHPAD_UTIL_PROCESS_PROCESS_MEMORY_MAC_H_ #include #include @@ -23,11 +23,14 @@ #include "base/mac/scoped_mach_vm.h" #include "base/macros.h" +#include "util/misc/address_types.h" +#include "util/misc/initialization_state_dcheck.h" +#include "util/process/process_memory.h" namespace crashpad { //! \brief Accesses the memory of another Mach task. -class TaskMemory { +class ProcessMemoryMac : public ProcessMemory { public: //! \brief A memory region mapped from another Mach task. //! @@ -82,33 +85,25 @@ class TaskMemory { size_t user_size_; // The outer class needs to be able to call this class’ private constructor. - friend class TaskMemory; + friend class ProcessMemoryMac; DISALLOW_COPY_AND_ASSIGN(MappedMemory); }; - //! \param[in] task A send right to the target task’s task port. This object + ProcessMemoryMac(); + ~ProcessMemoryMac() {} + + //! \brief Initializes this object to read the memory of a task with the + //! provided task port. + //! + //! This method must be called successfully prior to calling any other method + //! in this class. + //! + //! \param[in] task A send right to the target task's task port. This object //! does not take ownership of the send right. - explicit TaskMemory(task_t task); - - ~TaskMemory() {} - - //! \brief Copies memory from the target task into a caller-provided buffer in - //! the current task. //! - //! \param[in] address The address, in the target task’s address space, of the - //! memory region to copy. - //! \param[in] size The size, in bytes, of the memory region to copy. \a - //! buffer must be at least this size. - //! \param[out] buffer The buffer into which the contents of the other task’s - //! memory will be copied. - //! - //! \return `true` on success, with \a buffer filled appropriately. `false` on - //! failure, with a warning logged. Failures can occur, for example, when - //! encountering unmapped or unreadable pages. - //! - //! \sa ReadMapped() - bool Read(mach_vm_address_t address, size_t size, void* buffer); + //! \return `true` on success, `false` on failure with a message logged. + bool Initialize(task_t task); //! \brief Maps memory from the target task into the current task. //! @@ -124,56 +119,17 @@ class TaskMemory { //! requested. On faliure, `nullptr`, with a warning logged. Failures can //! occur, for example, when encountering unmapped or unreadable pages. std::unique_ptr ReadMapped(mach_vm_address_t address, - size_t size); - - //! \brief Reads a `NUL`-terminated C string from the target task into a - //! string in the current task. - //! - //! The length of the string need not be known ahead of time. This method will - //! read contiguous memory until a `NUL` terminator is found. - //! - //! \param[in] address The address, in the target task’s address space, of the - //! string to copy. - //! \param[out] string The string read from the other task. - //! - //! \return `true` on success, with \a string set appropriately. `false` on - //! failure, with a warning logged. Failures can occur, for example, when - //! encountering unmapped or unreadable pages. - //! - //! \sa MappedMemory::ReadCString() - bool ReadCString(mach_vm_address_t address, std::string* string); - - //! \brief Reads a `NUL`-terminated C string from the target task into a - //! string in the current task. - //! - //! \param[in] address The address, in the target task’s address space, of the - //! string to copy. - //! \param[in] size The maximum number of bytes to read. The string is - //! required to be `NUL`-terminated within this many bytes. - //! \param[out] string The string read from the other task. - //! - //! \return `true` on success, with \a string set appropriately. `false` on - //! failure, with a warning logged. Failures can occur, for example, when - //! a `NUL` terminator is not found within \a size bytes, or when - //! encountering unmapped or unreadable pages. - //! - //! \sa MappedMemory::ReadCString() - bool ReadCStringSizeLimited(mach_vm_address_t address, - mach_vm_size_t size, - std::string* string); + size_t size) const; private: - // The common internal implementation shared by the ReadCString*() methods. - bool ReadCStringInternal(mach_vm_address_t address, - bool has_size, - mach_vm_size_t size, - std::string* string); + ssize_t ReadUpTo(VMAddress address, size_t size, void* buffer) const override; task_t task_; // weak + InitializationStateDcheck initialized_; - DISALLOW_COPY_AND_ASSIGN(TaskMemory); + DISALLOW_COPY_AND_ASSIGN(ProcessMemoryMac); }; } // namespace crashpad -#endif // CRASHPAD_UTIL_MACH_TASK_MEMORY_H_ +#endif // CRASHPAD_UTIL_PROCESS_PROCESS_MEMORY_MAC_H_ diff --git a/util/mach/task_memory_test.cc b/util/process/process_memory_mac_test.cc similarity index 53% rename from util/mach/task_memory_test.cc rename to util/process/process_memory_mac_test.cc index d9f0cb77..d801bb14 100644 --- a/util/mach/task_memory_test.cc +++ b/util/process/process_memory_mac_test.cc @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "util/mach/task_memory.h" +#include "util/process/process_memory_mac.h" #include #include @@ -31,7 +31,7 @@ namespace crashpad { namespace test { namespace { -TEST(TaskMemory, ReadSelf) { +TEST(ProcessMemoryMac, ReadMappedSelf) { vm_address_t address = 0; constexpr vm_size_t kSize = 4 * PAGE_SIZE; kern_return_t kr = @@ -44,63 +44,49 @@ TEST(TaskMemory, ReadSelf) { region[index] = (index % 256) ^ ((index >> 8) % 256); } - TaskMemory memory(mach_task_self()); + ProcessMemoryMac memory; + ASSERT_TRUE(memory.Initialize(mach_task_self())); - // This tests using both the Read() and ReadMapped() interfaces. std::string result(kSize, '\0'); - std::unique_ptr mapped; + std::unique_ptr mapped; // Ensure that the entire region can be read. - ASSERT_TRUE(memory.Read(address, kSize, &result[0])); - EXPECT_EQ(memcmp(region, &result[0], kSize), 0); ASSERT_TRUE((mapped = memory.ReadMapped(address, kSize))); EXPECT_EQ(memcmp(region, mapped->data(), kSize), 0); - // Ensure that a read of length 0 succeeds and doesn’t touch the result. + // Ensure that a read of length 0 succeeds and doesn't touch the result. result.assign(kSize, '\0'); std::string zeroes = result; - ASSERT_TRUE(memory.Read(address, 0, &result[0])); - EXPECT_EQ(result, zeroes); ASSERT_TRUE((mapped = memory.ReadMapped(address, 0))); + EXPECT_EQ(result, zeroes); // Ensure that a read starting at an unaligned address works. - ASSERT_TRUE(memory.Read(address + 1, kSize - 1, &result[0])); - EXPECT_EQ(memcmp(region + 1, &result[0], kSize - 1), 0); ASSERT_TRUE((mapped = memory.ReadMapped(address + 1, kSize - 1))); EXPECT_EQ(memcmp(region + 1, mapped->data(), kSize - 1), 0); // Ensure that a read ending at an unaligned address works. - ASSERT_TRUE(memory.Read(address, kSize - 1, &result[0])); - EXPECT_EQ(memcmp(region, &result[0], kSize - 1), 0); ASSERT_TRUE((mapped = memory.ReadMapped(address, kSize - 1))); EXPECT_EQ(memcmp(region, mapped->data(), kSize - 1), 0); // Ensure that a read starting and ending at unaligned addresses works. - ASSERT_TRUE(memory.Read(address + 1, kSize - 2, &result[0])); - EXPECT_EQ(memcmp(region + 1, &result[0], kSize - 2), 0); ASSERT_TRUE((mapped = memory.ReadMapped(address + 1, kSize - 2))); EXPECT_EQ(memcmp(region + 1, mapped->data(), kSize - 2), 0); // Ensure that a read of exactly one page works. - ASSERT_TRUE(memory.Read(address + PAGE_SIZE, PAGE_SIZE, &result[0])); - EXPECT_EQ(memcmp(region + PAGE_SIZE, &result[0], PAGE_SIZE), 0); ASSERT_TRUE((mapped = memory.ReadMapped(address + PAGE_SIZE, PAGE_SIZE))); EXPECT_EQ(memcmp(region + PAGE_SIZE, mapped->data(), PAGE_SIZE), 0); // Ensure that a read of a single byte works. - ASSERT_TRUE(memory.Read(address + 2, 1, &result[0])); - EXPECT_EQ(result[0], region[2]); ASSERT_TRUE((mapped = memory.ReadMapped(address + 2, 1))); EXPECT_EQ(reinterpret_cast(mapped->data())[0], region[2]); - // Ensure that a read of length zero works and doesn’t touch the data. + // Ensure that a read of length zero works and doesn't touch the data. result[0] = 'M'; - ASSERT_TRUE(memory.Read(address + 3, 0, &result[0])); - EXPECT_EQ(result[0], 'M'); ASSERT_TRUE((mapped = memory.ReadMapped(address + 3, 0))); + EXPECT_EQ(result[0], 'M'); } -TEST(TaskMemory, ReadSelfUnmapped) { +TEST(ProcessMemoryMac, ReadSelfUnmapped) { vm_address_t address = 0; constexpr vm_size_t kSize = 2 * PAGE_SIZE; kern_return_t kr = @@ -110,7 +96,7 @@ TEST(TaskMemory, ReadSelfUnmapped) { char* region = reinterpret_cast(address); for (size_t index = 0; index < kSize; ++index) { - // Don’t include any NUL bytes, because ReadCString stops when it encounters + // Don't include any NUL bytes, because ReadCString stops when it encounters // a NUL. region[index] = (index % 255) + 1; } @@ -119,7 +105,8 @@ TEST(TaskMemory, ReadSelfUnmapped) { mach_task_self(), address + PAGE_SIZE, PAGE_SIZE, FALSE, VM_PROT_NONE); ASSERT_EQ(kr, KERN_SUCCESS) << MachErrorMessage(kr, "vm_protect"); - TaskMemory memory(mach_task_self()); + ProcessMemoryMac memory; + ASSERT_TRUE(memory.Initialize(mach_task_self())); std::string result(kSize, '\0'); EXPECT_FALSE(memory.Read(address, kSize, &result[0])); @@ -130,7 +117,7 @@ TEST(TaskMemory, ReadSelfUnmapped) { EXPECT_TRUE(memory.Read(address + PAGE_SIZE - 1, 1, &result[0])); // Do the same thing with the ReadMapped() interface. - std::unique_ptr mapped; + std::unique_ptr mapped; EXPECT_FALSE((mapped = memory.ReadMapped(address, kSize))); EXPECT_FALSE((mapped = memory.ReadMapped(address + 1, kSize - 1))); EXPECT_FALSE((mapped = memory.ReadMapped(address + PAGE_SIZE, 1))); @@ -161,80 +148,7 @@ TEST(TaskMemory, ReadSelfUnmapped) { EXPECT_TRUE((mapped = memory.ReadMapped(address + PAGE_SIZE - 1, 1))); } -// This function consolidates the cast from a char* to mach_vm_address_t in one -// location when reading from the current task. -bool ReadCStringSelf(TaskMemory* memory, - const char* pointer, - std::string* result) { - return memory->ReadCString(FromPointerCast(pointer), - result); -} - -TEST(TaskMemory, ReadCStringSelf) { - TaskMemory memory(mach_task_self()); - std::string result; - - const char kConstCharEmpty[] = ""; - ASSERT_TRUE(ReadCStringSelf(&memory, kConstCharEmpty, &result)); - EXPECT_TRUE(result.empty()); - EXPECT_EQ(result, kConstCharEmpty); - - const char kConstCharShort[] = "A short const char[]"; - ASSERT_TRUE(ReadCStringSelf(&memory, kConstCharShort, &result)); - EXPECT_FALSE(result.empty()); - EXPECT_EQ(result, kConstCharShort); - - static const char kStaticConstCharEmpty[] = ""; - ASSERT_TRUE(ReadCStringSelf(&memory, kStaticConstCharEmpty, &result)); - EXPECT_TRUE(result.empty()); - EXPECT_EQ(result, kStaticConstCharEmpty); - - static const char kStaticConstCharShort[] = "A short static const char[]"; - ASSERT_TRUE(ReadCStringSelf(&memory, kStaticConstCharShort, &result)); - EXPECT_FALSE(result.empty()); - EXPECT_EQ(result, kStaticConstCharShort); - - constexpr char kConstexprCharEmpty[] = ""; - ASSERT_TRUE(ReadCStringSelf(&memory, kConstexprCharEmpty, &result)); - EXPECT_TRUE(result.empty()); - EXPECT_EQ(result, kConstexprCharEmpty); - - constexpr char kConstexprCharShort[] = "A short constexpr char[]"; - ASSERT_TRUE(ReadCStringSelf(&memory, kConstexprCharShort, &result)); - EXPECT_FALSE(result.empty()); - EXPECT_EQ(result, kConstexprCharShort); - - static constexpr char kStaticConstexprCharEmpty[] = ""; - ASSERT_TRUE(ReadCStringSelf(&memory, kStaticConstexprCharEmpty, &result)); - EXPECT_TRUE(result.empty()); - EXPECT_EQ(result, kStaticConstexprCharEmpty); - - static constexpr char kStaticConstexprCharShort[] = - "A short static constexpr char[]"; - ASSERT_TRUE(ReadCStringSelf(&memory, kStaticConstexprCharShort, &result)); - EXPECT_FALSE(result.empty()); - EXPECT_EQ(result, kStaticConstexprCharShort); - - std::string string_short("A short std::string in a function"); - ASSERT_TRUE(ReadCStringSelf(&memory, &string_short[0], &result)); - EXPECT_FALSE(result.empty()); - EXPECT_EQ(result, string_short); - - std::string string_long; - constexpr size_t kStringLongSize = 4 * PAGE_SIZE; - for (size_t index = 0; index < kStringLongSize; ++index) { - // Don’t include any NUL bytes, because ReadCString stops when it encounters - // a NUL. - string_long.append(1, (index % 255) + 1); - } - ASSERT_EQ(string_long.size(), kStringLongSize); - ASSERT_TRUE(ReadCStringSelf(&memory, &string_long[0], &result)); - EXPECT_FALSE(result.empty()); - EXPECT_EQ(result.size(), kStringLongSize); - EXPECT_EQ(result, string_long); -} - -TEST(TaskMemory, ReadCStringSelfUnmapped) { +TEST(ProcessMemoryMac, ReadCStringSelfUnmapped) { vm_address_t address = 0; constexpr vm_size_t kSize = 2 * PAGE_SIZE; kern_return_t kr = @@ -244,7 +158,7 @@ TEST(TaskMemory, ReadCStringSelfUnmapped) { char* region = reinterpret_cast(address); for (size_t index = 0; index < kSize; ++index) { - // Don’t include any NUL bytes, because ReadCString stops when it encounters + // Don't include any NUL bytes, because ReadCString stops when it encounters // a NUL. region[index] = (index % 255) + 1; } @@ -253,7 +167,8 @@ TEST(TaskMemory, ReadCStringSelfUnmapped) { mach_task_self(), address + PAGE_SIZE, PAGE_SIZE, FALSE, VM_PROT_NONE); ASSERT_EQ(kr, KERN_SUCCESS) << MachErrorMessage(kr, "vm_protect"); - TaskMemory memory(mach_task_self()); + ProcessMemoryMac memory; + ASSERT_TRUE(memory.Initialize(mach_task_self())); std::string result; EXPECT_FALSE(memory.ReadCString(address, &result)); @@ -287,161 +202,6 @@ TEST(TaskMemory, ReadCStringSelfUnmapped) { EXPECT_EQ(result, region); } -// This function consolidates the cast from a char* to mach_vm_address_t in one -// location when reading from the current task. -bool ReadCStringSizeLimitedSelf(TaskMemory* memory, - const char* pointer, - size_t size, - std::string* result) { - return memory->ReadCStringSizeLimited( - FromPointerCast(pointer), size, result); -} - -TEST(TaskMemory, ReadCStringSizeLimited_ConstCharEmpty) { - TaskMemory memory(mach_task_self()); - std::string result; - - static constexpr char kConstCharEmpty[] = ""; - ASSERT_TRUE(ReadCStringSizeLimitedSelf( - &memory, kConstCharEmpty, arraysize(kConstCharEmpty), &result)); - EXPECT_TRUE(result.empty()); - EXPECT_EQ(result, kConstCharEmpty); - - result.clear(); - ASSERT_TRUE(ReadCStringSizeLimitedSelf( - &memory, kConstCharEmpty, arraysize(kConstCharEmpty) + 1, &result)); - EXPECT_TRUE(result.empty()); - EXPECT_EQ(result, kConstCharEmpty); - - result.clear(); - ASSERT_TRUE(ReadCStringSizeLimitedSelf(&memory, kConstCharEmpty, 0, &result)); - EXPECT_TRUE(result.empty()); - EXPECT_EQ(result, kConstCharEmpty); -} - -TEST(TaskMemory, ReadCStringSizeLimited_ConstCharShort) { - TaskMemory memory(mach_task_self()); - std::string result; - - static constexpr char kConstCharShort[] = "A short const char[]"; - ASSERT_TRUE(ReadCStringSizeLimitedSelf( - &memory, kConstCharShort, arraysize(kConstCharShort), &result)); - EXPECT_FALSE(result.empty()); - EXPECT_EQ(result, kConstCharShort); - - result.clear(); - ASSERT_TRUE(ReadCStringSizeLimitedSelf( - &memory, kConstCharShort, arraysize(kConstCharShort) + 1, &result)); - EXPECT_FALSE(result.empty()); - EXPECT_EQ(result, kConstCharShort); - - ASSERT_FALSE(ReadCStringSizeLimitedSelf( - &memory, kConstCharShort, arraysize(kConstCharShort) - 1, &result)); -} - -TEST(TaskMemory, ReadCStringSizeLimited_StaticConstCharEmpty) { - TaskMemory memory(mach_task_self()); - std::string result; - - static constexpr char kStaticConstCharEmpty[] = ""; - ASSERT_TRUE(ReadCStringSizeLimitedSelf(&memory, - kStaticConstCharEmpty, - arraysize(kStaticConstCharEmpty), - &result)); - EXPECT_TRUE(result.empty()); - EXPECT_EQ(result, kStaticConstCharEmpty); - - result.clear(); - ASSERT_TRUE(ReadCStringSizeLimitedSelf(&memory, - kStaticConstCharEmpty, - arraysize(kStaticConstCharEmpty) + 1, - &result)); - EXPECT_TRUE(result.empty()); - EXPECT_EQ(result, kStaticConstCharEmpty); - - result.clear(); - ASSERT_TRUE( - ReadCStringSizeLimitedSelf(&memory, kStaticConstCharEmpty, 0, &result)); - EXPECT_TRUE(result.empty()); - EXPECT_EQ(result, kStaticConstCharEmpty); -} - -TEST(TaskMemory, ReadCStringSizeLimited_StaticConstCharShort) { - TaskMemory memory(mach_task_self()); - std::string result; - - static constexpr char kStaticConstCharShort[] = - "A short static constexpr char[]"; - ASSERT_TRUE(ReadCStringSizeLimitedSelf(&memory, - kStaticConstCharShort, - arraysize(kStaticConstCharShort), - &result)); - EXPECT_FALSE(result.empty()); - EXPECT_EQ(result, kStaticConstCharShort); - - result.clear(); - ASSERT_TRUE(ReadCStringSizeLimitedSelf(&memory, - kStaticConstCharShort, - arraysize(kStaticConstCharShort) + 1, - &result)); - EXPECT_FALSE(result.empty()); - EXPECT_EQ(result, kStaticConstCharShort); - - ASSERT_FALSE(ReadCStringSizeLimitedSelf(&memory, - kStaticConstCharShort, - arraysize(kStaticConstCharShort) - 1, - &result)); -} - -TEST(TaskMemory, ReadCStringSizeLimited_StringShort) { - TaskMemory memory(mach_task_self()); - std::string result; - - std::string string_short("A short std::string in a function"); - ASSERT_TRUE(ReadCStringSizeLimitedSelf( - &memory, &string_short[0], string_short.size() + 1, &result)); - EXPECT_FALSE(result.empty()); - EXPECT_EQ(result, string_short); - - result.clear(); - ASSERT_TRUE(ReadCStringSizeLimitedSelf( - &memory, &string_short[0], string_short.size() + 2, &result)); - EXPECT_FALSE(result.empty()); - EXPECT_EQ(result, string_short); - - ASSERT_FALSE(ReadCStringSizeLimitedSelf( - &memory, &string_short[0], string_short.size(), &result)); -} - -TEST(TaskMemory, ReadCStringSizeLimited_StringLong) { - TaskMemory memory(mach_task_self()); - std::string result; - - std::string string_long; - constexpr size_t kStringLongSize = 4 * PAGE_SIZE; - for (size_t index = 0; index < kStringLongSize; ++index) { - // Don’t include any NUL bytes, because ReadCString stops when it encounters - // a NUL. - string_long.append(1, (index % 255) + 1); - } - ASSERT_EQ(string_long.size(), kStringLongSize); - ASSERT_TRUE(ReadCStringSizeLimitedSelf( - &memory, &string_long[0], string_long.size() + 1, &result)); - EXPECT_FALSE(result.empty()); - EXPECT_EQ(result.size(), kStringLongSize); - EXPECT_EQ(result, string_long); - - result.clear(); - ASSERT_TRUE(ReadCStringSizeLimitedSelf( - &memory, &string_long[0], string_long.size() + 2, &result)); - EXPECT_FALSE(result.empty()); - EXPECT_EQ(result.size(), kStringLongSize); - EXPECT_EQ(result, string_long); - - ASSERT_FALSE(ReadCStringSizeLimitedSelf( - &memory, &string_long[0], string_long.size(), &result)); -} - bool IsAddressMapped(vm_address_t address) { vm_address_t region_address = address; vm_size_t region_size; @@ -472,16 +232,17 @@ bool IsAddressMapped(vm_address_t address) { return false; } -TEST(TaskMemory, MappedMemoryDeallocates) { - // This tests that once a TaskMemory::MappedMemory object is destroyed, it - // releases the mapped memory that it owned. Technically, this test is not +TEST(ProcessMemoryMac, MappedMemoryDeallocates) { + // This tests that once a ProcessMemoryMac::MappedMemory object is destroyed, + // it releases the mapped memory that it owned. Technically, this test is not // valid because after the mapping is released, something else (on another // thread) might wind up mapped in the same address. In the test environment, - // hopefully there are either no other threads or they’re all quiescent, so + // hopefully there are either no other threads or they're all quiescent, so // nothing else should wind up mapped in the address. - TaskMemory memory(mach_task_self()); - std::unique_ptr mapped; + ProcessMemoryMac memory; + ASSERT_TRUE(memory.Initialize(mach_task_self())); + std::unique_ptr mapped; static constexpr char kTestBuffer[] = "hello!"; mach_vm_address_t test_address = @@ -515,10 +276,11 @@ TEST(TaskMemory, MappedMemoryDeallocates) { EXPECT_FALSE(IsAddressMapped(mapped_last_address)); } -TEST(TaskMemory, MappedMemoryReadCString) { - // This tests the behavior of TaskMemory::MappedMemory::ReadCString(). - TaskMemory memory(mach_task_self()); - std::unique_ptr mapped; +TEST(ProcessMemoryMac, MappedMemoryReadCString) { + // This tests the behavior of ProcessMemoryMac::MappedMemory::ReadCString(). + ProcessMemoryMac memory; + ASSERT_TRUE(memory.Initialize(mach_task_self())); + std::unique_ptr mapped; static constexpr char kTestBuffer[] = "0\0" "2\0" "45\0" "789"; const mach_vm_address_t kTestAddress = diff --git a/util/process/process_memory_native.h b/util/process/process_memory_native.h index 19fa805a..39bfc893 100644 --- a/util/process/process_memory_native.h +++ b/util/process/process_memory_native.h @@ -18,6 +18,10 @@ #include "util/process/process_memory_fuchsia.h" #elif defined(OS_LINUX) || defined(OS_ANDROID) #include "util/process/process_memory_linux.h" +#elif defined(OS_WIN) +#include "util/process/process_memory_win.h" +#elif defined(OS_MACOSX) +#include "util/process/process_memory_mac.h" #endif namespace crashpad { @@ -27,6 +31,10 @@ namespace crashpad { using ProcessMemoryNative = ProcessMemoryFuchsia; #elif defined(OS_LINUX) || defined(OS_ANDROID) using ProcessMemoryNative = ProcessMemoryLinux; +#elif defined(OS_WIN) +using ProcessMemoryNative = ProcessMemoryWin; +#elif defined(OS_MACOSX) +using ProcessMemoryNative = ProcessMemoryMac; #else #error Port. #endif diff --git a/util/process/process_memory_range.cc b/util/process/process_memory_range.cc index 32d805de..caa4315f 100644 --- a/util/process/process_memory_range.cc +++ b/util/process/process_memory_range.cc @@ -67,7 +67,7 @@ bool ProcessMemoryRange::RestrictRange(VMAddress base, VMSize size) { } bool ProcessMemoryRange::Read(VMAddress address, - size_t size, + VMSize size, void* buffer) const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); CheckedVMAddressRange read_range(range_.Is64Bit(), address, size); @@ -79,14 +79,14 @@ bool ProcessMemoryRange::Read(VMAddress address, } bool ProcessMemoryRange::ReadCStringSizeLimited(VMAddress address, - size_t size, + VMSize size, std::string* string) const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); if (!range_.ContainsValue(address)) { LOG(ERROR) << "read out of range"; return false; } - size = std::min(static_cast(size), range_.End() - address); + size = std::min(size, range_.End() - address); return memory_->ReadCStringSizeLimited(address, size, string); } diff --git a/util/process/process_memory_range.h b/util/process/process_memory_range.h index 2b654ba1..aabee498 100644 --- a/util/process/process_memory_range.h +++ b/util/process/process_memory_range.h @@ -97,7 +97,7 @@ class ProcessMemoryRange { //! //! \return `true` on success, with \a buffer filled appropriately. `false` on //! failure, with a message logged. - bool Read(VMAddress address, size_t size, void* buffer) const; + bool Read(VMAddress address, VMSize size, void* buffer) const; //! \brief Reads a `NUL`-terminated C string from the target process into a //! string in the current process. @@ -113,7 +113,7 @@ class ProcessMemoryRange { //! a `NUL` terminator is not found within \a size bytes, or when //! encountering unmapped or unreadable pages. bool ReadCStringSizeLimited(VMAddress address, - size_t size, + VMSize size, std::string* string) const; private: diff --git a/util/process/process_memory_range_test.cc b/util/process/process_memory_range_test.cc index 1c785068..2df2b5cb 100644 --- a/util/process/process_memory_range_test.cc +++ b/util/process/process_memory_range_test.cc @@ -14,22 +14,15 @@ #include "util/process/process_memory_range.h" -#include - #include #include "base/logging.h" +#include "base/stl_util.h" #include "build/build_config.h" #include "gtest/gtest.h" +#include "test/process_type.h" #include "util/misc/from_pointer_cast.h" - -#if defined(OS_FUCHSIA) -#include - -#include "util/process/process_memory_fuchsia.h" -#else -#include "util/process/process_memory_linux.h" -#endif +#include "util/process/process_memory_native.h" namespace crashpad { namespace test { @@ -41,21 +34,14 @@ 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; #else constexpr bool is_64_bit = false; #endif // ARCH_CPU_64_BITS - ProcessMemoryLinux memory; - ASSERT_TRUE(memory.Initialize(pid)); -#endif // OS_FUCHSIA + ProcessMemoryNative memory; + ASSERT_TRUE(memory.Initialize(GetSelfProcess())); ProcessMemoryRange range; ASSERT_TRUE(range.Initialize(&memory, is_64_bit)); @@ -73,28 +59,28 @@ TEST(ProcessMemoryRange, Basic) { auto string1_addr = FromPointerCast(kTestObject.string1); auto string2_addr = FromPointerCast(kTestObject.string2); ASSERT_TRUE(range.ReadCStringSizeLimited( - string1_addr, arraysize(kTestObject.string1), &string)); + string1_addr, base::size(kTestObject.string1), &string)); EXPECT_STREQ(string.c_str(), kTestObject.string1); ASSERT_TRUE(range.ReadCStringSizeLimited( - string2_addr, arraysize(kTestObject.string2), &string)); + string2_addr, base::size(kTestObject.string2), &string)); EXPECT_STREQ(string.c_str(), kTestObject.string2); // Limit the range to remove access to string2. ProcessMemoryRange range2; ASSERT_TRUE(range2.Initialize(range)); ASSERT_TRUE( - range2.RestrictRange(string1_addr, arraysize(kTestObject.string1))); + range2.RestrictRange(string1_addr, base::size(kTestObject.string1))); EXPECT_TRUE(range2.ReadCStringSizeLimited( - string1_addr, arraysize(kTestObject.string1), &string)); + string1_addr, base::size(kTestObject.string1), &string)); EXPECT_FALSE(range2.ReadCStringSizeLimited( - string2_addr, arraysize(kTestObject.string2), &string)); + string2_addr, base::size(kTestObject.string2), &string)); EXPECT_FALSE(range2.Read(object_addr, sizeof(object), &object)); // String reads fail if the NUL terminator is outside the range. ASSERT_TRUE(range2.RestrictRange(string1_addr, strlen(kTestObject.string1))); EXPECT_FALSE(range2.ReadCStringSizeLimited( - string1_addr, arraysize(kTestObject.string1), &string)); + string1_addr, base::size(kTestObject.string1), &string)); // New range outside the old range. EXPECT_FALSE(range2.RestrictRange(string1_addr - 1, 1)); diff --git a/util/process/process_memory_sanitized.cc b/util/process/process_memory_sanitized.cc new file mode 100644 index 00000000..cafe5aef --- /dev/null +++ b/util/process/process_memory_sanitized.cc @@ -0,0 +1,65 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/process/process_memory_sanitized.h" + +#include +#include +#include +#include + +#include +#include + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" + +namespace crashpad { + +ProcessMemorySanitized::ProcessMemorySanitized() + : ProcessMemory(), memory_(nullptr), whitelist_() {} + +ProcessMemorySanitized::~ProcessMemorySanitized() {} + +bool ProcessMemorySanitized::Initialize( + const ProcessMemory* memory, + const std::vector>* whitelist) { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + memory_ = memory; + if (whitelist) + whitelist_ = *whitelist; + INITIALIZATION_STATE_SET_VALID(initialized_); + return true; +} + +ssize_t ProcessMemorySanitized::ReadUpTo(VMAddress address, + size_t size, + void* buffer) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + VMAddress end = address + size; + for (auto&& entry : whitelist_) { + if (address >= entry.first && address < entry.second && + end >= entry.first && end <= entry.second) { + return memory_->ReadUpTo(address, size, buffer); + } + } + + DLOG(ERROR) + << "ProcessMemorySanitized failed to read unwhitelisted region. address=" + << address << " size=" << size; + return 0; +} + +} // namespace crashpad diff --git a/util/process/process_memory_sanitized.h b/util/process/process_memory_sanitized.h new file mode 100644 index 00000000..44439636 --- /dev/null +++ b/util/process/process_memory_sanitized.h @@ -0,0 +1,62 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_PROCESS_PROCESS_MEMORY_SANITIZED_H_ +#define CRASHPAD_UTIL_PROCESS_PROCESS_MEMORY_SANITIZED_H_ + +#include + +#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 Sanitized access to the memory of another process. +class ProcessMemorySanitized final : public ProcessMemory { + public: + ProcessMemorySanitized(); + ~ProcessMemorySanitized(); + + //! \brief Initializes this object to read memory from the underlying + //! \a memory object if the memory range is in the provided \a whitelist. + //! + //! This method must be called successfully prior to calling any other method + //! in this class. + //! + //! \param[in] memory The memory object to read whitelisted regions from. + //! \param[in] whitelist A whitelist of memory regions. + //! + //! \return `true` on success, `false` on failure with a message logged. + bool Initialize( + const ProcessMemory* memory, + const std::vector>* whitelist); + + private: + ssize_t ReadUpTo(VMAddress address, size_t size, void* buffer) const override; + + const ProcessMemory* memory_; + InitializationStateDcheck initialized_; + std::vector> whitelist_; + + DISALLOW_COPY_AND_ASSIGN(ProcessMemorySanitized); +}; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_PROCESS_PROCESS_MEMORY_SANITIZED_H_ diff --git a/util/process/process_memory_sanitized_test.cc b/util/process/process_memory_sanitized_test.cc new file mode 100644 index 00000000..ff5c9445 --- /dev/null +++ b/util/process/process_memory_sanitized_test.cc @@ -0,0 +1,64 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/process/process_memory_sanitized.h" + +#include "gtest/gtest.h" +#include "test/process_type.h" +#include "util/misc/from_pointer_cast.h" +#include "util/process/process_memory_native.h" + +namespace crashpad { +namespace test { +namespace { + +TEST(ProcessMemorySanitized, DenyOnEmptyWhitelist) { + ProcessMemoryNative memory; + ASSERT_TRUE(memory.Initialize(GetSelfProcess())); + + char c = 42; + char out; + + ProcessMemorySanitized san_null; + san_null.Initialize(&memory, nullptr); + EXPECT_FALSE(san_null.Read(FromPointerCast(&c), 1, &out)); + + std::vector> whitelist; + ProcessMemorySanitized san_blank; + san_blank.Initialize(&memory, &whitelist); + EXPECT_FALSE(san_blank.Read(FromPointerCast(&c), 1, &out)); +} + +TEST(ProcessMemorySanitized, WhitelistingWorks) { + ProcessMemoryNative memory; + ASSERT_TRUE(memory.Initialize(GetSelfProcess())); + + char str[4] = "ABC"; + char out[4]; + + std::vector> whitelist; + whitelist.push_back(std::make_pair(FromPointerCast(str + 1), + FromPointerCast(str + 2))); + + ProcessMemorySanitized sanitized; + sanitized.Initialize(&memory, &whitelist); + + EXPECT_FALSE(sanitized.Read(FromPointerCast(str), 1, &out)); + EXPECT_TRUE(sanitized.Read(FromPointerCast(str + 1), 1, &out)); + EXPECT_FALSE(sanitized.Read(FromPointerCast(str + 2), 1, &out)); +} + +} // namespace +} // namespace test +} // namespace crashpad diff --git a/util/process/process_memory_test.cc b/util/process/process_memory_test.cc index 741e58d6..3f5db24f 100644 --- a/util/process/process_memory_test.cc +++ b/util/process/process_memory_test.cc @@ -15,28 +15,93 @@ #include "util/process/process_memory.h" #include -#include -#include #include +#include "base/process/process_metrics.h" +#include "build/build_config.h" #include "gtest/gtest.h" #include "test/errors.h" #include "test/multiprocess.h" #include "test/multiprocess_exec.h" #include "test/process_type.h" +#include "test/scoped_guarded_page.h" #include "util/file/file_io.h" #include "util/misc/from_pointer_cast.h" -#include "util/posix/scoped_mmap.h" #include "util/process/process_memory_native.h" +#if defined(OS_MACOSX) +#include "test/mac/mach_multiprocess.h" +#endif // defined(OS_MACOSX) + namespace crashpad { namespace test { namespace { +// On macOS the ProcessMemoryTests require accessing the child process' task +// port which requires root or a code signing entitlement. To account for this +// we implement an adaptor class that wraps MachMultiprocess on macOS, because +// it shares the child's task port, and makes it behave like MultiprocessExec. +#if defined(OS_MACOSX) +class MultiprocessAdaptor : public MachMultiprocess { + public: + void SetChildTestMainFunction(const std::string& function_name) { + test_function_ = function_name; + } + + ProcessType ChildProcess() { return ChildTask(); } + + // Helpers to get I/O handles in the child process + static FileHandle OutputHandle() { + CHECK_NE(write_pipe_handle_, -1); + return write_pipe_handle_; + } + + static FileHandle InputHandle() { + CHECK_NE(read_pipe_handle_, -1); + return read_pipe_handle_; + } + + private: + virtual void Parent() = 0; + + void MachMultiprocessParent() override { Parent(); } + + void MachMultiprocessChild() override { + read_pipe_handle_ = ReadPipeHandle(); + write_pipe_handle_ = WritePipeHandle(); + internal::CheckedInvokeMultiprocessChild(test_function_); + } + + std::string test_function_; + + static FileHandle read_pipe_handle_; + static FileHandle write_pipe_handle_; +}; + +FileHandle MultiprocessAdaptor::read_pipe_handle_ = -1; +FileHandle MultiprocessAdaptor::write_pipe_handle_ = -1; +#else +class MultiprocessAdaptor : public MultiprocessExec { + public: + static FileHandle OutputHandle() { + return StdioFileHandle(StdioStream::kStandardOutput); + } + + static FileHandle InputHandle() { + return StdioFileHandle(StdioStream::kStandardInput); + } + + private: + virtual void Parent() = 0; + + void MultiprocessParent() override { Parent(); } +}; +#endif // defined(OS_MACOSX) + void DoChildReadTestSetup(size_t* region_size, std::unique_ptr* region) { - *region_size = 4 * getpagesize(); + *region_size = 4 * base::GetPageSize(); region->reset(new char[*region_size]); for (size_t index = 0; index < *region_size; ++index) { (*region)[index] = index % 256; @@ -47,17 +112,17 @@ CRASHPAD_CHILD_TEST_MAIN(ReadTestChild) { size_t region_size; std::unique_ptr region; DoChildReadTestSetup(®ion_size, ®ion); - FileHandle out = StdioFileHandle(StdioStream::kStandardOutput); + FileHandle out = MultiprocessAdaptor::OutputHandle(); CheckedWriteFile(out, ®ion_size, sizeof(region_size)); VMAddress address = FromPointerCast(region.get()); CheckedWriteFile(out, &address, sizeof(address)); - CheckedReadFileAtEOF(StdioFileHandle(StdioStream::kStandardInput)); + CheckedReadFileAtEOF(MultiprocessAdaptor::InputHandle()); return 0; } -class ReadTest : public MultiprocessExec { +class ReadTest : public MultiprocessAdaptor { public: - ReadTest() : MultiprocessExec() { + ReadTest() : MultiprocessAdaptor() { SetChildTestMainFunction("ReadTestChild"); } @@ -73,7 +138,7 @@ class ReadTest : public MultiprocessExec { void RunAgainstChild() { Run(); } private: - void MultiprocessParent() override { + void Parent() override { size_t region_size; VMAddress region; ASSERT_TRUE( @@ -120,7 +185,7 @@ class ReadTest : public MultiprocessExec { } // Ensure that a read of exactly one page works. - size_t page_size = getpagesize(); + size_t page_size = base::GetPageSize(); ASSERT_GE(region_size, page_size + page_size); ASSERT_TRUE(memory.Read(address + page_size, page_size, result.get())); for (size_t i = 0; i < page_size; ++i) { @@ -154,7 +219,7 @@ constexpr char kConstCharShort[] = "A short const char[]"; std::string MakeLongString() { std::string long_string; - const size_t kStringLongSize = 4 * getpagesize(); + const size_t kStringLongSize = 4 * base::GetPageSize(); for (size_t index = 0; index < kStringLongSize; ++index) { long_string.push_back((index % 255) + 1); } @@ -184,23 +249,22 @@ CRASHPAD_CHILD_TEST_MAIN(ReadCStringTestChild) { &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)); + CheckedWriteFile( + MultiprocessAdaptor::OutputHandle(), &address, sizeof(address)); }; write_address(const_empty); write_address(const_short); write_address(local_empty); write_address(local_short); write_address(long_string.c_str()); - CheckedReadFileAtEOF(StdioFileHandle(StdioStream::kStandardInput)); + CheckedReadFileAtEOF(MultiprocessAdaptor::InputHandle()); return 0; } -class ReadCStringTest : public MultiprocessExec { +class ReadCStringTest : public MultiprocessAdaptor { public: ReadCStringTest(bool limit_size) - : MultiprocessExec(), limit_size_(limit_size) { + : MultiprocessAdaptor(), limit_size_(limit_size) { SetChildTestMainFunction("ReadCStringTestChild"); } @@ -222,7 +286,7 @@ class ReadCStringTest : public MultiprocessExec { void RunAgainstChild() { Run(); } private: - void MultiprocessParent() override { + void Parent() override { #define DECLARE_AND_READ_ADDRESS(name) \ VMAddress name; \ ASSERT_TRUE(ReadFileExactly(ReadPipeHandle(), &name, sizeof(name))); @@ -241,6 +305,23 @@ class ReadCStringTest : public MultiprocessExec { long_string_address); } + void Compare(ProcessMemory& memory, VMAddress address, const char* str) { + std::string result; + if (limit_size_) { + ASSERT_TRUE( + memory.ReadCStringSizeLimited(address, strlen(str) + 1, &result)); + EXPECT_EQ(result, str); + ASSERT_TRUE( + memory.ReadCStringSizeLimited(address, strlen(str) + 2, &result)); + EXPECT_EQ(result, str); + EXPECT_FALSE( + memory.ReadCStringSizeLimited(address, strlen(str), &result)); + } else { + ASSERT_TRUE(memory.ReadCString(address, &result)); + EXPECT_EQ(result, str); + } + } + void DoTest(ProcessType process, VMAddress const_empty_address, VMAddress const_short_address, @@ -250,51 +331,12 @@ class ReadCStringTest : public MultiprocessExec { ProcessMemoryNative memory; ASSERT_TRUE(memory.Initialize(process)); - std::string result; - - if (limit_size_) { - ASSERT_TRUE(memory.ReadCStringSizeLimited( - const_empty_address, arraysize(kConstCharEmpty), &result)); - EXPECT_EQ(result, kConstCharEmpty); - - ASSERT_TRUE(memory.ReadCStringSizeLimited( - const_short_address, arraysize(kConstCharShort), &result)); - EXPECT_EQ(result, kConstCharShort); - EXPECT_FALSE(memory.ReadCStringSizeLimited( - const_short_address, arraysize(kConstCharShort) - 1, &result)); - - ASSERT_TRUE( - memory.ReadCStringSizeLimited(local_empty_address, 1, &result)); - EXPECT_EQ(result, ""); - - ASSERT_TRUE(memory.ReadCStringSizeLimited( - local_short_address, strlen(SHORT_LOCAL_STRING) + 1, &result)); - EXPECT_EQ(result, SHORT_LOCAL_STRING); - EXPECT_FALSE(memory.ReadCStringSizeLimited( - local_short_address, strlen(SHORT_LOCAL_STRING), &result)); - - std::string long_string_for_comparison = MakeLongString(); - ASSERT_TRUE(memory.ReadCStringSizeLimited( - long_string_address, long_string_for_comparison.size() + 1, &result)); - EXPECT_EQ(result, long_string_for_comparison); - EXPECT_FALSE(memory.ReadCStringSizeLimited( - long_string_address, long_string_for_comparison.size(), &result)); - } else { - ASSERT_TRUE(memory.ReadCString(const_empty_address, &result)); - EXPECT_EQ(result, kConstCharEmpty); - - ASSERT_TRUE(memory.ReadCString(const_short_address, &result)); - EXPECT_EQ(result, kConstCharShort); - - ASSERT_TRUE(memory.ReadCString(local_empty_address, &result)); - EXPECT_EQ(result, ""); - - ASSERT_TRUE(memory.ReadCString(local_short_address, &result)); - EXPECT_EQ(result, SHORT_LOCAL_STRING); - - ASSERT_TRUE(memory.ReadCString(long_string_address, &result)); - EXPECT_EQ(result, MakeLongString()); - } + Compare(memory, const_empty_address, kConstCharEmpty); + Compare(memory, const_short_address, kConstCharShort); + Compare(memory, local_empty_address, ""); + Compare(memory, local_short_address, SHORT_LOCAL_STRING); + std::string long_string_for_comparison = MakeLongString(); + Compare(memory, long_string_address, long_string_for_comparison.c_str()); } const bool limit_size_; @@ -322,101 +364,60 @@ TEST(ProcessMemory, ReadCStringSizeLimitedChild) { test.RunAgainstChild(); } -void DoReadUnmappedChildMainSetup(ScopedMmap* pages, - VMAddress* address, - size_t* page_size, - size_t* region_size) { - *page_size = getpagesize(); - *region_size = 2 * (*page_size); - if (!pages->ResetMmap(nullptr, - *region_size, - PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, - -1, - 0)) { - ADD_FAILURE(); - return; - } - - *address = pages->addr_as(); - - char* region = pages->addr_as(); - for (size_t index = 0; index < *region_size; ++index) { +void DoReadUnmappedChildMainSetup(void* page) { + char* region = reinterpret_cast(page); + for (size_t index = 0; index < base::GetPageSize(); ++index) { region[index] = index % 256; } - - EXPECT_TRUE(pages->ResetAddrLen(region, *page_size)); } CRASHPAD_CHILD_TEST_MAIN(ReadUnmappedChildMain) { - ScopedMmap pages; - VMAddress address = 0; - size_t page_size, region_size; - DoReadUnmappedChildMainSetup(&pages, &address, &page_size, ®ion_size); - FileHandle out = StdioFileHandle(StdioStream::kStandardOutput); + ScopedGuardedPage pages; + VMAddress address = reinterpret_cast(pages.Pointer()); + DoReadUnmappedChildMainSetup(pages.Pointer()); + FileHandle out = MultiprocessAdaptor::OutputHandle(); CheckedWriteFile(out, &address, sizeof(address)); - CheckedWriteFile(out, &page_size, sizeof(page_size)); - CheckedWriteFile(out, ®ion_size, sizeof(region_size)); - CheckedReadFileAtEOF(StdioFileHandle(StdioStream::kStandardInput)); + CheckedReadFileAtEOF(MultiprocessAdaptor::InputHandle()); return 0; } -class ReadUnmappedTest : public MultiprocessExec { +// This test only supports running against a child process because +// ScopedGuardedPage is not thread-safe. +class ReadUnmappedTest : public MultiprocessAdaptor { public: - ReadUnmappedTest() : MultiprocessExec() { + ReadUnmappedTest() : MultiprocessAdaptor() { SetChildTestMainFunction("ReadUnmappedChildMain"); } - void RunAgainstSelf() { - ScopedMmap pages; - VMAddress address = 0; - size_t page_size, region_size; - DoReadUnmappedChildMainSetup(&pages, &address, &page_size, ®ion_size); - DoTest(GetSelfProcess(), address, page_size, region_size); - } - void RunAgainstChild() { Run(); } private: - void MultiprocessParent() override { + void Parent() override { VMAddress address = 0; - size_t page_size, region_size; ASSERT_TRUE(ReadFileExactly(ReadPipeHandle(), &address, sizeof(address))); - ASSERT_TRUE( - ReadFileExactly(ReadPipeHandle(), &page_size, sizeof(page_size))); - ASSERT_TRUE( - ReadFileExactly(ReadPipeHandle(), ®ion_size, sizeof(region_size))); - DoTest(ChildProcess(), address, page_size, region_size); + DoTest(ChildProcess(), address); } - void DoTest(ProcessType process, - VMAddress address, - size_t page_size, - size_t region_size) { + void DoTest(ProcessType process, VMAddress address) { ProcessMemoryNative memory; ASSERT_TRUE(memory.Initialize(process)); VMAddress page_addr1 = address; - VMAddress page_addr2 = page_addr1 + page_size; + VMAddress page_addr2 = page_addr1 + base::GetPageSize(); - std::unique_ptr result(new char[region_size]); - EXPECT_TRUE(memory.Read(page_addr1, page_size, result.get())); + std::unique_ptr result(new char[base::GetPageSize() * 2]); + EXPECT_TRUE(memory.Read(page_addr1, base::GetPageSize(), result.get())); EXPECT_TRUE(memory.Read(page_addr2 - 1, 1, result.get())); - EXPECT_FALSE(memory.Read(page_addr1, region_size, result.get())); - EXPECT_FALSE(memory.Read(page_addr2, page_size, result.get())); + EXPECT_FALSE( + memory.Read(page_addr1, base::GetPageSize() * 2, result.get())); + EXPECT_FALSE(memory.Read(page_addr2, base::GetPageSize(), result.get())); EXPECT_FALSE(memory.Read(page_addr2 - 1, 2, result.get())); } DISALLOW_COPY_AND_ASSIGN(ReadUnmappedTest); }; -TEST(ProcessMemory, ReadUnmappedSelf) { - ReadUnmappedTest test; - ASSERT_FALSE(testing::Test::HasFailure()); - test.RunAgainstSelf(); -} - TEST(ProcessMemory, ReadUnmappedChild) { ReadUnmappedTest test; ASSERT_FALSE(testing::Test::HasFailure()); @@ -428,9 +429,13 @@ constexpr size_t kChildProcessStringLength = 10; class StringDataInChildProcess { public: // This constructor only makes sense in the child process. - explicit StringDataInChildProcess(const char* cstring) + explicit StringDataInChildProcess(const char* cstring, bool valid) : address_(FromPointerCast(cstring)) { - memcpy(expected_value_, cstring, kChildProcessStringLength + 1); + if (valid) { + memcpy(expected_value_, cstring, kChildProcessStringLength + 1); + } else { + memset(expected_value_, 0xff, kChildProcessStringLength + 1); + } } void Write(FileHandle out) { @@ -457,22 +462,10 @@ class StringDataInChildProcess { }; void DoCStringUnmappedTestSetup( - ScopedMmap* pages, + void* page, 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) { + char* region = reinterpret_cast(page); + for (size_t index = 0; index < base::GetPageSize(); ++index) { region[index] = 1 + index % 255; } @@ -481,63 +474,53 @@ void DoCStringUnmappedTestSetup( string1[kChildProcessStringLength] = '\0'; // A string near the end of the mapped region - char* string2 = region + page_size - kChildProcessStringLength * 2; + char* string2 = region + base::GetPageSize() - kChildProcessStringLength * 2; string2[kChildProcessStringLength] = '\0'; // A string that crosses from the mapped into the unmapped region - char* string3 = region + page_size - kChildProcessStringLength + 1; - string3[kChildProcessStringLength] = '\0'; + char* string3 = region + base::GetPageSize() - kChildProcessStringLength + 1; // A string entirely in the unmapped region - char* string4 = region + page_size + 10; - string4[kChildProcessStringLength] = '\0'; + char* string4 = region + base::GetPageSize() + 10; - strings->push_back(StringDataInChildProcess(string1)); - strings->push_back(StringDataInChildProcess(string2)); - strings->push_back(StringDataInChildProcess(string3)); - strings->push_back(StringDataInChildProcess(string4)); - - EXPECT_TRUE(pages->ResetAddrLen(region, page_size)); + strings->push_back(StringDataInChildProcess(string1, true)); + strings->push_back(StringDataInChildProcess(string2, true)); + strings->push_back(StringDataInChildProcess(string3, false)); + strings->push_back(StringDataInChildProcess(string4, false)); } CRASHPAD_CHILD_TEST_MAIN(ReadCStringUnmappedChildMain) { - ScopedMmap pages; + ScopedGuardedPage pages; std::vector strings; - DoCStringUnmappedTestSetup(&pages, &strings); - FileHandle out = StdioFileHandle(StdioStream::kStandardOutput); + DoCStringUnmappedTestSetup(pages.Pointer(), &strings); + FileHandle out = MultiprocessAdaptor::OutputHandle(); strings[0].Write(out); strings[1].Write(out); strings[2].Write(out); strings[3].Write(out); - CheckedReadFileAtEOF(StdioFileHandle(StdioStream::kStandardInput)); + CheckedReadFileAtEOF(MultiprocessAdaptor::InputHandle()); return 0; } -class ReadCStringUnmappedTest : public MultiprocessExec { +// This test only supports running against a child process because +// ScopedGuardedPage is not thread-safe. +class ReadCStringUnmappedTest : public MultiprocessAdaptor { public: ReadCStringUnmappedTest(bool limit_size) - : MultiprocessExec(), limit_size_(limit_size) { + : MultiprocessAdaptor(), limit_size_(limit_size) { SetChildTestMainFunction("ReadCStringUnmappedChildMain"); } - void RunAgainstSelf() { - ScopedMmap pages; - std::vector strings; - DoCStringUnmappedTestSetup(&pages, &strings); - DoTest(GetSelfProcess(), strings); - } - void RunAgainstChild() { Run(); } private: - void MultiprocessParent() override { + void Parent() 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); + ASSERT_NO_FATAL_FAILURE(DoTest(ChildProcess(), strings)); } void DoTest(ProcessType process, @@ -574,24 +557,12 @@ class ReadCStringUnmappedTest : public MultiprocessExec { DISALLOW_COPY_AND_ASSIGN(ReadCStringUnmappedTest); }; -TEST(ProcessMemory, ReadCStringUnmappedSelf) { - ReadCStringUnmappedTest test(/* limit_size= */ false); - ASSERT_FALSE(testing::Test::HasFailure()); - test.RunAgainstSelf(); -} - TEST(ProcessMemory, ReadCStringUnmappedChild) { ReadCStringUnmappedTest test(/* limit_size= */ false); ASSERT_FALSE(testing::Test::HasFailure()); test.RunAgainstChild(); } -TEST(ProcessMemory, ReadCStringSizeLimitedUnmappedSelf) { - ReadCStringUnmappedTest test(/* limit_size= */ true); - ASSERT_FALSE(testing::Test::HasFailure()); - test.RunAgainstSelf(); -} - TEST(ProcessMemory, ReadCStringSizeLimitedUnmappedChild) { ReadCStringUnmappedTest test(/* limit_size= */ true); ASSERT_FALSE(testing::Test::HasFailure()); diff --git a/util/process/process_memory_win.cc b/util/process/process_memory_win.cc new file mode 100644 index 00000000..f716102b --- /dev/null +++ b/util/process/process_memory_win.cc @@ -0,0 +1,118 @@ +// Copyright 2018 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/process/process_memory_win.h" + +#include + +#include +#include + +#include "base/logging.h" +#include "base/numerics/safe_conversions.h" +#include "base/process/process_metrics.h" +#include "base/strings/stringprintf.h" + +namespace crashpad { + +ProcessMemoryWin::ProcessMemoryWin() + : ProcessMemory(), handle_(), process_info_(), initialized_() {} + +ProcessMemoryWin::~ProcessMemoryWin() {} + +bool ProcessMemoryWin::Initialize(HANDLE handle) { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + + handle_ = handle; + if (!process_info_.Initialize(handle)) { + LOG(ERROR) << "Failed to initialize ProcessInfo."; + return false; + } + + INITIALIZATION_STATE_SET_VALID(initialized_); + return true; +} + +ssize_t ProcessMemoryWin::ReadUpTo(VMAddress address, + size_t size, + void* buffer) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + DCHECK_LE(size, (size_t)std::numeric_limits::max()); + + SIZE_T size_out = 0; + BOOL success = ReadProcessMemory( + handle_, reinterpret_cast(address), buffer, size, &size_out); + if (success) + return base::checked_cast(size_out); + + if (GetLastError() == ERROR_PARTIAL_COPY) { + // If we can not read the entire section, perform a short read of the first + // page instead. This is necessary to support ReadCString(). + size_t short_read = + base::GetPageSize() - (address & (base::GetPageSize() - 1)); + success = ReadProcessMemory(handle_, + reinterpret_cast(address), + buffer, + short_read, + &size_out); + if (success) + return base::checked_cast(size_out); + } + + PLOG(ERROR) << "ReadMemory at 0x" << std::hex << address << std::dec << " of " + << size << " bytes failed"; + return -1; +} + +size_t ProcessMemoryWin::ReadAvailableMemory(VMAddress address, + size_t size, + void* buffer) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + DCHECK_LE(size, (size_t)std::numeric_limits::max()); + + if (size == 0) + return 0; + + auto ranges = process_info_.GetReadableRanges( + CheckedRange(address, size)); + + // We only read up until the first unavailable byte, so we only read from the + // first range. If we have no ranges, then no bytes were accessible anywhere + // in the range. + if (ranges.empty()) { + LOG(ERROR) << base::StringPrintf( + "range at 0x%llx, size 0x%zx completely inaccessible", address, size); + return 0; + } + + // If the start address was adjusted, we couldn't read even the first + // requested byte. + if (ranges.front().base() != address) { + LOG(ERROR) << base::StringPrintf( + "start of range at 0x%llx, size 0x%zx inaccessible", address, size); + return 0; + } + + DCHECK_LE(ranges.front().size(), size); + + ssize_t result = ReadUpTo(ranges.front().base(), + base::checked_cast(ranges.front().size()), + buffer); + if (result < 0) + return 0; + + return base::checked_cast(result); +} + +} // namespace crashpad diff --git a/util/process/process_memory_win.h b/util/process/process_memory_win.h new file mode 100644 index 00000000..2856900e --- /dev/null +++ b/util/process/process_memory_win.h @@ -0,0 +1,66 @@ +// Copyright 2018 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_PROCESS_PROCESS_MEMORY_WIN_H_ +#define CRASHPAD_UTIL_PROCESS_PROCESS_MEMORY_WIN_H_ + +#include + +#include "base/macros.h" +#include "util/misc/address_types.h" +#include "util/misc/initialization_state_dcheck.h" +#include "util/process/process_memory.h" +#include "util/win/process_info.h" + +namespace crashpad { + +//! \brief Accesses the memory of another Windows process. +class ProcessMemoryWin final : public ProcessMemory { + public: + ProcessMemoryWin(); + ~ProcessMemoryWin(); + + //! \brief Initializes this object to read the memory of a process with the + //! provided handle. + //! + //! This method must be called successfully prior to calling any other method + //! in this class. + //! + //! \param[in] handle The HANDLE of a target process. + //! + //! \return `true` on success, `false` on failure with a message logged. + bool Initialize(HANDLE handle); + + //! \brief Attempts to read \a size bytes from the target process starting at + //! address \a address into \a buffer. If some of the specified range is + //! not accessible, reads up to the first inaccessible byte. + //! + //! \return The actual number of bytes read. + size_t ReadAvailableMemory(VMAddress address, + size_t num_bytes, + void* buffer) const; + + private: + ssize_t ReadUpTo(VMAddress address, size_t size, void* buffer) const override; + + HANDLE handle_; + ProcessInfo process_info_; + InitializationStateDcheck initialized_; + + DISALLOW_COPY_AND_ASSIGN(ProcessMemoryWin); +}; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_PROCESS_PROCESS_MEMORY_WIN_H_ diff --git a/util/stdlib/string_number_conversion.cc b/util/stdlib/string_number_conversion.cc index 859a8333..64211844 100644 --- a/util/stdlib/string_number_conversion.cc +++ b/util/stdlib/string_number_conversion.cc @@ -97,15 +97,34 @@ struct StringToUnsignedIntTraits } }; -struct StringToInt64Traits - : public StringToSignedIntegerTraits { +struct StringToLongTraits + : public StringToSignedIntegerTraits { static LongType Convert(const char* str, char** end, int base) { return strtoll(str, end, base); } }; -struct StringToUnsignedInt64Traits - : public StringToUnsignedIntegerTraits { +struct StringToUnsignedLongTraits + : public StringToUnsignedIntegerTraits { + static LongType Convert(const char* str, char** end, int base) { + if (str[0] == '-') { + *end = const_cast(str); + return 0; + } + return strtoull(str, end, base); + } +}; + +struct StringToLongLongTraits + : public StringToSignedIntegerTraits { + static LongType Convert(const char* str, char** end, int base) { + return strtoll(str, end, base); + } +}; + +struct StringToUnsignedLongLongTraits + : public StringToUnsignedIntegerTraits { static LongType Convert(const char* str, char** end, int base) { if (str[0] == '-') { *end = const_cast(str); @@ -136,7 +155,7 @@ bool StringToIntegerInternal(const std::string& string, end != string.data() + string.length()) { return false; } - *number = result; + *number = static_cast(result); return true; } @@ -152,12 +171,21 @@ bool StringToNumber(const std::string& string, unsigned int* number) { return StringToIntegerInternal(string, number); } -bool StringToNumber(const std::string& string, int64_t* number) { - return StringToIntegerInternal(string, number); +bool StringToNumber(const std::string& string, long* number) { + return StringToIntegerInternal(string, number); } -bool StringToNumber(const std::string& string, uint64_t* number) { - return StringToIntegerInternal(string, number); +bool StringToNumber(const std::string& string, unsigned long* number) { + return StringToIntegerInternal(string, number); +} + +bool StringToNumber(const std::string& string, long long* number) { + return StringToIntegerInternal(string, number); +} + +bool StringToNumber(const std::string& string, unsigned long long* number) { + return StringToIntegerInternal(string, + number); } } // namespace crashpad diff --git a/util/stdlib/string_number_conversion.h b/util/stdlib/string_number_conversion.h index b5f1d44a..69e7fdd6 100644 --- a/util/stdlib/string_number_conversion.h +++ b/util/stdlib/string_number_conversion.h @@ -56,8 +56,10 @@ namespace crashpad { //! where such prefix recognition is desirable. bool StringToNumber(const std::string& string, int* number); bool StringToNumber(const std::string& string, unsigned int* number); -bool StringToNumber(const std::string& string, int64_t* number); -bool StringToNumber(const std::string& string, uint64_t* number); +bool StringToNumber(const std::string& string, long* number); +bool StringToNumber(const std::string& string, unsigned long* number); +bool StringToNumber(const std::string& string, long long* number); +bool StringToNumber(const std::string& string, unsigned long long* number); //! \} } // namespace crashpad diff --git a/util/stdlib/string_number_conversion_test.cc b/util/stdlib/string_number_conversion_test.cc index d855c8d7..760dc4a8 100644 --- a/util/stdlib/string_number_conversion_test.cc +++ b/util/stdlib/string_number_conversion_test.cc @@ -16,30 +16,39 @@ #include +#include #include +#include -#include "base/macros.h" +#include "base/stl_util.h" #include "gtest/gtest.h" -namespace crashpad { -namespace test { -namespace { +#define STRINGIFY(a) STR(a) +#define STR(a) #a -TEST(StringNumberConversion, StringToInt) { - static constexpr struct { - const char* string; - bool valid; - int value; - } kTestData[] = { +template +struct TestSpecification { + const char* string; + bool valid; + TValueType value; +}; + +// Signed 32-bit test data +template ::value && + std::is_signed::value && + (sizeof(TIntType) == 4)>::type* = nullptr> +static constexpr std::array, 61> kTestDataFunc() { + return {{ {"", false, 0}, {"0", true, 0}, {"1", true, 1}, - {"2147483647", true, std::numeric_limits::max()}, + {"2147483647", true, std::numeric_limits::max()}, {"2147483648", false, 0}, {"4294967295", false, 0}, {"4294967296", false, 0}, {"-1", true, -1}, - {"-2147483648", true, std::numeric_limits::min()}, + {"-2147483648", true, std::numeric_limits::min()}, {"-2147483649", false, 0}, {"00", true, 0}, {"01", true, 1}, @@ -50,12 +59,12 @@ TEST(StringNumberConversion, StringToInt) { {"+0x20", true, 32}, {"0xf", true, 15}, {"0xg", false, 0}, - {"0x7fffffff", true, std::numeric_limits::max()}, - {"0x7FfFfFfF", true, std::numeric_limits::max()}, + {"0x7fffffff", true, std::numeric_limits::max()}, + {"0x7FfFfFfF", true, std::numeric_limits::max()}, {"0x80000000", false, 0}, {"0xFFFFFFFF", false, 0}, {"-0x7fffffff", true, -2147483647}, - {"-0x80000000", true, std::numeric_limits::min()}, + {"-0x80000000", true, std::numeric_limits::min()}, {"-0x80000001", false, 0}, {"-0xffffffff", false, 0}, {"0x100000000", false, 0}, @@ -92,45 +101,22 @@ TEST(StringNumberConversion, StringToInt) { {"9223372036854775809", false, 0}, {"18446744073709551615", false, 0}, {"18446744073709551616", false, 0}, - }; - - for (size_t index = 0; index < arraysize(kTestData); ++index) { - int value; - bool valid = StringToNumber(kTestData[index].string, &value); - if (kTestData[index].valid) { - EXPECT_TRUE(valid) << "index " << index << ", string " - << kTestData[index].string; - if (valid) { - EXPECT_EQ(value, kTestData[index].value) - << "index " << index << ", string " << kTestData[index].string; - } - } else { - EXPECT_FALSE(valid) << "index " << index << ", string " - << kTestData[index].string << ", value " << value; - } - } - - // Ensure that embedded NUL characters are treated as bad input. The string - // is split to avoid MSVC warning: - // "decimal digit terminates octal escape sequence". - static constexpr char input[] = "6\000" "6"; - std::string input_string(input, arraysize(input) - 1); - int output; - EXPECT_FALSE(StringToNumber(input_string, &output)); + }}; } -TEST(StringNumberConversion, StringToUnsignedInt) { - static constexpr struct { - const char* string; - bool valid; - unsigned int value; - } kTestData[] = { +// Unsigned 32-bit test data +template ::value && + !std::is_signed::value && + (sizeof(TIntType) == 4)>::type* = nullptr> +static constexpr std::array, 61> kTestDataFunc() { + return {{ {"", false, 0}, {"0", true, 0}, {"1", true, 1}, {"2147483647", true, 2147483647}, {"2147483648", true, 2147483648}, - {"4294967295", true, std::numeric_limits::max()}, + {"4294967295", true, std::numeric_limits::max()}, {"4294967296", false, 0}, {"-1", false, 0}, {"-2147483648", false, 0}, @@ -186,9 +172,121 @@ TEST(StringNumberConversion, StringToUnsignedInt) { {"9223372036854775809", false, 0}, {"18446744073709551615", false, 0}, {"18446744073709551616", false, 0}, - }; + }}; +} - for (size_t index = 0; index < arraysize(kTestData); ++index) { +// Signed 64-bit test data +template ::value && + std::is_signed::value && + (sizeof(TIntType) == 8)>::type* = nullptr> +static constexpr std::array, 24> kTestDataFunc() { + return {{ + {"", false, 0}, + {"0", true, 0}, + {"1", true, 1}, + {"2147483647", true, 2147483647}, + {"2147483648", true, 2147483648}, + {"4294967295", true, 4294967295}, + {"4294967296", true, 4294967296}, + {"9223372036854775807", true, std::numeric_limits::max()}, + {"9223372036854775808", false, 0}, + {"18446744073709551615", false, 0}, + {"18446744073709551616", false, 0}, + {"-1", true, -1}, + {"-2147483648", true, INT64_C(-2147483648)}, + {"-2147483649", true, INT64_C(-2147483649)}, + {"-9223372036854775808", true, std::numeric_limits::min()}, + {"-9223372036854775809", false, 0}, + {"0x7fffffffffffffff", true, std::numeric_limits::max()}, + {"0x8000000000000000", false, 0}, + {"0xffffffffffffffff", false, 0}, + {"0x10000000000000000", false, 0}, + {"-0x7fffffffffffffff", true, -9223372036854775807}, + {"-0x8000000000000000", true, std::numeric_limits::min()}, + {"-0x8000000000000001", false, 0}, + {"0x7Fffffffffffffff", true, std::numeric_limits::max()}, + }}; +} + +// Unsigned 64-bit test data +template ::value && + !std::is_signed::value && + (sizeof(TIntType) == 8)>::type* = nullptr> +static constexpr std::array, 25> kTestDataFunc() { + return {{ + {"", false, 0}, + {"0", true, 0}, + {"1", true, 1}, + {"2147483647", true, 2147483647}, + {"2147483648", true, 2147483648}, + {"4294967295", true, 4294967295}, + {"4294967296", true, 4294967296}, + {"9223372036854775807", true, 9223372036854775807}, + {"9223372036854775808", true, 9223372036854775808u}, + {"18446744073709551615", true, std::numeric_limits::max()}, + {"18446744073709551616", false, 0}, + {"-1", false, 0}, + {"-2147483648", false, 0}, + {"-2147483649", false, 0}, + {"-2147483648", false, 0}, + {"-9223372036854775808", false, 0}, + {"-9223372036854775809", false, 0}, + {"0x7fffffffffffffff", true, 9223372036854775807}, + {"0x8000000000000000", true, 9223372036854775808u}, + {"0xffffffffffffffff", true, std::numeric_limits::max()}, + {"0x10000000000000000", false, 0}, + {"-0x7fffffffffffffff", false, 0}, + {"-0x8000000000000000", false, 0}, + {"-0x8000000000000001", false, 0}, + {"0xFfffffffffffffff", true, std::numeric_limits::max()}, + }}; +} + +// This string is split to avoid MSVC warning: +// "decimal digit terminates octal escape sequence". +static constexpr char kEmbeddedNullInputRaw[] = "6\000" "6"; + +namespace crashpad { +namespace test { +namespace { + +TEST(StringNumberConversion, StringToInt) { + static_assert(sizeof(int) == 4, "Test only configured for 32-bit int."); + static constexpr auto kTestData = kTestDataFunc(); + + for (size_t index = 0; index < kTestData.size(); ++index) { + int value; + bool valid = StringToNumber(kTestData[index].string, &value); + if (kTestData[index].valid) { + EXPECT_TRUE(valid) << "index " << index << ", string " + << kTestData[index].string; + if (valid) { + EXPECT_EQ(value, kTestData[index].value) + << "index " << index << ", string " << kTestData[index].string; + } + } else { + EXPECT_FALSE(valid) << "index " << index << ", string " + << kTestData[index].string << ", value " << value; + } + } + + // Ensure that embedded NUL characters are treated as bad input. The string + // is split to avoid MSVC warning: + // "decimal digit terminates octal escape sequence". + int output; + std::string kEmbeddedNullInput(kEmbeddedNullInputRaw, + base::size(kEmbeddedNullInputRaw) - 1); + EXPECT_FALSE(StringToNumber(kEmbeddedNullInput, &output)); +} + +TEST(StringNumberConversion, StringToUnsignedInt) { + static_assert(sizeof(unsigned int) == 4, + "Test only configured for 32-bit unsigned int."); + static constexpr auto kTestData = kTestDataFunc(); + + for (size_t index = 0; index < kTestData.size(); ++index) { unsigned int value; bool valid = StringToNumber(kTestData[index].string, &value); if (kTestData[index].valid) { @@ -207,46 +305,20 @@ TEST(StringNumberConversion, StringToUnsignedInt) { // Ensure that embedded NUL characters are treated as bad input. The string // is split to avoid MSVC warning: // "decimal digit terminates octal escape sequence". - static constexpr char input[] = "6\000" "6"; - std::string input_string(input, arraysize(input) - 1); unsigned int output; - EXPECT_FALSE(StringToNumber(input_string, &output)); + std::string kEmbeddedNullInput(kEmbeddedNullInputRaw, + base::size(kEmbeddedNullInputRaw) - 1); + EXPECT_FALSE(StringToNumber(kEmbeddedNullInput, &output)); } -TEST(StringNumberConversion, StringToInt64) { - static constexpr struct { - const char* string; - bool valid; - int64_t value; - } kTestData[] = { - {"", false, 0}, - {"0", true, 0}, - {"1", true, 1}, - {"2147483647", true, 2147483647}, - {"2147483648", true, 2147483648}, - {"4294967295", true, 4294967295}, - {"4294967296", true, 4294967296}, - {"9223372036854775807", true, std::numeric_limits::max()}, - {"9223372036854775808", false, 0}, - {"18446744073709551615", false, 0}, - {"18446744073709551616", false, 0}, - {"-1", true, -1}, - {"-2147483648", true, INT64_C(-2147483648)}, - {"-2147483649", true, INT64_C(-2147483649)}, - {"-9223372036854775808", true, std::numeric_limits::min()}, - {"-9223372036854775809", false, 0}, - {"0x7fffffffffffffff", true, std::numeric_limits::max()}, - {"0x8000000000000000", false, 0}, - {"0xffffffffffffffff", false, 0}, - {"0x10000000000000000", false, 0}, - {"-0x7fffffffffffffff", true, -9223372036854775807}, - {"-0x8000000000000000", true, std::numeric_limits::min()}, - {"-0x8000000000000001", false, 0}, - {"0x7Fffffffffffffff", true, std::numeric_limits::max()}, - }; +TEST(StringNumberConversion, StringToLong) { + static_assert( + sizeof(long) == 4 || sizeof(long) == 8, + "Test not configured for " STRINGIFY(__SIZEOF_LONG__) "-byte long"); + static constexpr auto kTestData = kTestDataFunc(); - for (size_t index = 0; index < arraysize(kTestData); ++index) { - int64_t value; + for (size_t index = 0; index < kTestData.size(); ++index) { + long value; bool valid = StringToNumber(kTestData[index].string, &value); if (kTestData[index].valid) { EXPECT_TRUE(valid) << "index " << index << ", string " @@ -262,41 +334,58 @@ TEST(StringNumberConversion, StringToInt64) { } } -TEST(StringNumberConversion, StringToUnsignedInt64) { - static constexpr struct { - const char* string; - bool valid; - uint64_t value; - } kTestData[] = { - {"", false, 0}, - {"0", true, 0}, - {"1", true, 1}, - {"2147483647", true, 2147483647}, - {"2147483648", true, 2147483648}, - {"4294967295", true, 4294967295}, - {"4294967296", true, 4294967296}, - {"9223372036854775807", true, 9223372036854775807}, - {"9223372036854775808", true, 9223372036854775808u}, - {"18446744073709551615", true, std::numeric_limits::max()}, - {"18446744073709551616", false, 0}, - {"-1", false, 0}, - {"-2147483648", false, 0}, - {"-2147483649", false, 0}, - {"-2147483648", false, 0}, - {"-9223372036854775808", false, 0}, - {"-9223372036854775809", false, 0}, - {"0x7fffffffffffffff", true, 9223372036854775807}, - {"0x8000000000000000", true, 9223372036854775808u}, - {"0xffffffffffffffff", true, std::numeric_limits::max()}, - {"0x10000000000000000", false, 0}, - {"-0x7fffffffffffffff", false, 0}, - {"-0x8000000000000000", false, 0}, - {"-0x8000000000000001", false, 0}, - {"0xFfffffffffffffff", true, std::numeric_limits::max()}, - }; +TEST(StringNumberConversion, StringToUnsignedLong) { + static_assert( + sizeof(long) == 4 || sizeof(long) == 8, + "Test not configured for " STRINGIFY(__SIZEOF_LONG__) "-byte long"); + static constexpr auto kTestData = kTestDataFunc(); - for (size_t index = 0; index < arraysize(kTestData); ++index) { - uint64_t value; + for (size_t index = 0; index < kTestData.size(); ++index) { + unsigned long value; + bool valid = StringToNumber(kTestData[index].string, &value); + if (kTestData[index].valid) { + EXPECT_TRUE(valid) << "index " << index << ", string " + << kTestData[index].string; + if (valid) { + EXPECT_EQ(value, kTestData[index].value) + << "index " << index << ", string " << kTestData[index].string; + } + } else { + EXPECT_FALSE(valid) << "index " << index << ", string " + << kTestData[index].string << ", value " << value; + } + } +} + +TEST(StringNumberConversion, StringToLongLong) { + static_assert(sizeof(long long) == 8, + "Test only configured for 64-bit long long."); + static constexpr auto kTestData = kTestDataFunc(); + + for (size_t index = 0; index < kTestData.size(); ++index) { + long long value; + bool valid = StringToNumber(kTestData[index].string, &value); + if (kTestData[index].valid) { + EXPECT_TRUE(valid) << "index " << index << ", string " + << kTestData[index].string; + if (valid) { + EXPECT_EQ(value, kTestData[index].value) + << "index " << index << ", string " << kTestData[index].string; + } + } else { + EXPECT_FALSE(valid) << "index " << index << ", string " + << kTestData[index].string << ", value " << value; + } + } +} + +TEST(StringNumberConversion, StringToUnsignedLongLong) { + static_assert(sizeof(unsigned long long) == 8, + "Test only configured for 64-bit unsigned long long."); + static constexpr auto kTestData = kTestDataFunc(); + + for (size_t index = 0; index < kTestData.size(); ++index) { + unsigned long long value; bool valid = StringToNumber(kTestData[index].string, &value); if (kTestData[index].valid) { EXPECT_TRUE(valid) << "index " << index << ", string " diff --git a/util/stdlib/strlcpy_test.cc b/util/stdlib/strlcpy_test.cc index 5d20e193..bfb00fe2 100644 --- a/util/stdlib/strlcpy_test.cc +++ b/util/stdlib/strlcpy_test.cc @@ -20,7 +20,7 @@ #include #include "base/format_macros.h" -#include "base/macros.h" +#include "base/stl_util.h" #include "base/strings/string16.h" #include "base/strings/stringprintf.h" #include "gtest/gtest.h" @@ -64,7 +64,7 @@ TEST(strlcpy, c16lcpy) { static constexpr base::char16 test_characters[] = {0x4d, 0xe9, 0x100, 0x151, 0x1e18}; - for (size_t index = 0; index < arraysize(test_characters); ++index) { + for (size_t index = 0; index < base::size(test_characters); ++index) { base::char16 test_character = test_characters[index]; SCOPED_TRACE(base::StringPrintf( "character index %" PRIuS ", character 0x%x", index, test_character)); @@ -78,13 +78,13 @@ TEST(strlcpy, c16lcpy) { EXPECT_EQ(c16lcpy(destination.data, test_string.c_str(), - arraysize(destination.data)), + base::size(destination.data)), length); // Make sure that the destination buffer is NUL-terminated, and that as // much of the test string was copied as could fit. size_t expected_destination_length = - std::min(length, arraysize(destination.data) - 1); + std::min(length, base::size(destination.data) - 1); EXPECT_EQ(destination.data[expected_destination_length], '\0'); EXPECT_EQ(C16Len(destination.data), expected_destination_length); @@ -97,15 +97,15 @@ TEST(strlcpy, c16lcpy) { // of the buffer passed to c16lcpy. EXPECT_TRUE(C16Memcmp(expected_untouched.lead_guard, destination.lead_guard, - arraysize(destination.lead_guard)) == 0); + base::size(destination.lead_guard)) == 0); size_t expected_untouched_length = - arraysize(destination.data) - expected_destination_length - 1; + base::size(destination.data) - expected_destination_length - 1; EXPECT_TRUE(C16Memcmp(expected_untouched.data, &destination.data[expected_destination_length + 1], expected_untouched_length) == 0); EXPECT_TRUE(C16Memcmp(expected_untouched.trail_guard, destination.trail_guard, - arraysize(destination.trail_guard)) == 0); + base::size(destination.trail_guard)) == 0); } } } diff --git a/util/stdlib/strnlen.cc b/util/stdlib/strnlen.cc index a238728f..7ef8d3b1 100644 --- a/util/stdlib/strnlen.cc +++ b/util/stdlib/strnlen.cc @@ -14,7 +14,8 @@ #include "util/stdlib/strnlen.h" -#if defined(OS_MACOSX) && MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_7 +#if defined(OS_MACOSX) && !defined(OS_IOS) && \ + MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_7 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7 // Redeclare a method only available on Mac OS X 10.7 and later to suppress a diff --git a/util/stdlib/strnlen.h b/util/stdlib/strnlen.h index e85d8c7e..1db5f6e2 100644 --- a/util/stdlib/strnlen.h +++ b/util/stdlib/strnlen.h @@ -20,7 +20,7 @@ #include "build/build_config.h" -#if defined(OS_MACOSX) +#if defined(OS_MACOSX) && !defined(OS_IOS) #include #endif @@ -38,7 +38,7 @@ namespace crashpad { //! and not all systems’ standard libraries provide an implementation. size_t strnlen(const char* string, size_t max_length); -#if !defined(OS_MACOSX) || \ +#if !defined(OS_MACOSX) || defined(OS_IOS) || \ MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_7 inline size_t strnlen(const char* string, size_t max_length) { return ::strnlen(string, max_length); diff --git a/util/stdlib/thread_safe_vector_test.cc b/util/stdlib/thread_safe_vector_test.cc index 805360f4..1d26b09d 100644 --- a/util/stdlib/thread_safe_vector_test.cc +++ b/util/stdlib/thread_safe_vector_test.cc @@ -14,6 +14,7 @@ #include "util/stdlib/thread_safe_vector.h" +#include "base/stl_util.h" #include "gtest/gtest.h" #include "util/thread/thread.h" @@ -53,12 +54,12 @@ TEST(ThreadSafeVector, ThreadSafeVector) { EXPECT_TRUE(vector.empty()); ThreadSafeVectorTestThread threads[100]; - for (size_t index = 0; index < arraysize(threads); ++index) { + for (size_t index = 0; index < base::size(threads); ++index) { threads[index].SetTestParameters( &thread_safe_vector, static_cast(index * kElementsPerThread)); } - for (size_t index = 0; index < arraysize(threads); ++index) { + for (size_t index = 0; index < base::size(threads); ++index) { threads[index].Start(); if (index % 10 == 0) { @@ -75,8 +76,8 @@ TEST(ThreadSafeVector, ThreadSafeVector) { std::vector drained = thread_safe_vector.Drain(); vector.insert(vector.end(), drained.begin(), drained.end()); - bool found[arraysize(threads) * kElementsPerThread] = {}; - EXPECT_EQ(vector.size(), arraysize(found)); + bool found[base::size(threads) * kElementsPerThread] = {}; + EXPECT_EQ(vector.size(), base::size(found)); for (int element : vector) { EXPECT_FALSE(found[element]) << element; found[element] = true; diff --git a/util/stream/base94_output_stream.cc b/util/stream/base94_output_stream.cc new file mode 100644 index 00000000..a47d774b --- /dev/null +++ b/util/stream/base94_output_stream.cc @@ -0,0 +1,173 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/stream/base94_output_stream.h" + +#include + +#include "base/logging.h" +#include "base/numerics/safe_conversions.h" + +namespace crashpad { + +namespace { +// To improve the space efficiency, we can encode 14 bits into two symbols +// if 14-bit number is less than 94^2 which is total number can be encoded +// by 2 digits of base94 number, in another word, if 13 bit number is +// smaller than 643, we could read one more bit, because even if the 14th +// bit is 1, the 14-bit number doesn’t exceed the max value. +constexpr uint16_t kMaxValueOf14BitEncoding = (94 * 94 - 1) & 0x1FFF; + +constexpr size_t kMaxBuffer = 4096; + +inline uint8_t EncodeByte(uint8_t byte) { + DCHECK(byte < 94); + return (byte >= 94u) ? 0xff : (byte + '!'); +} + +inline uint8_t DecodeByte(uint8_t byte) { + DCHECK(byte >= '!' && byte <= '~'); + return std::min(static_cast(byte - '!'), static_cast(94)); +} + +} // namespace + +Base94OutputStream::Base94OutputStream( + Mode mode, + std::unique_ptr output_stream) + : mode_(mode), + output_stream_(std::move(output_stream)), + bit_buf_(0), + bit_count_(0), + symbol_buffer_(0), + flush_needed_(false), + flushed_(false) { + buffer_.reserve(kMaxBuffer); +} + +Base94OutputStream::~Base94OutputStream() { + DCHECK(!flush_needed_); +} + +bool Base94OutputStream::Write(const uint8_t* data, size_t size) { + DCHECK(!flushed_); + flush_needed_ = true; + return mode_ == Mode::kEncode ? Encode(data, size) : Decode(data, size); +} + +bool Base94OutputStream::Flush() { + flushed_ = true; + if (flush_needed_) { + flush_needed_ = false; + if (!((mode_ == Mode::kEncode) ? FinishEncoding() : FinishDecoding())) + return false; + } + return output_stream_->Flush(); +} + +bool Base94OutputStream::Encode(const uint8_t* data, size_t size) { + const uint8_t* cur = data; + while (size--) { + bit_buf_ |= *(cur++) << bit_count_; + bit_count_ += 8; + if (bit_count_ < 14) + continue; + + uint16_t block; + // Check if 13-bit or 14-bit data should be encoded. + if ((bit_buf_ & 0x1FFF) > kMaxValueOf14BitEncoding) { + block = bit_buf_ & 0x1FFF; + bit_buf_ >>= 13; + bit_count_ -= 13; + } else { + block = bit_buf_ & 0x3FFF; + bit_buf_ >>= 14; + bit_count_ -= 14; + } + buffer_.push_back(EncodeByte(block % 94)); + buffer_.push_back(EncodeByte(base::saturated_cast(block / 94))); + + if (buffer_.size() > kMaxBuffer - 2 && !WriteOutputStream()) + return false; + } + return WriteOutputStream(); +} + +bool Base94OutputStream::Decode(const uint8_t* data, size_t size) { + const uint8_t* cur = data; + while (size--) { + if (DecodeByte(*cur) == 94) { + LOG(ERROR) << "Decode: invalid input"; + return false; + } + if (symbol_buffer_ == 0) { + symbol_buffer_ = *cur; + cur++; + continue; + } + uint16_t v = DecodeByte(symbol_buffer_) + DecodeByte(*cur) * 94; + cur++; + symbol_buffer_ = 0; + bit_buf_ |= v << bit_count_; + bit_count_ += (v & 0x1FFF) > kMaxValueOf14BitEncoding ? 13 : 14; + while (bit_count_ > 7) { + buffer_.push_back(bit_buf_ & 0xff); + bit_buf_ >>= 8; + bit_count_ -= 8; + } + if (buffer_.size() > kMaxBuffer - 2) { + if (!WriteOutputStream()) + return false; + } + } + return WriteOutputStream(); +} + +bool Base94OutputStream::FinishEncoding() { + if (bit_count_ == 0) + return true; + // Up to 13 bits data is left over. + buffer_.push_back(EncodeByte(bit_buf_ % 94)); + if (bit_buf_ > 93 || bit_count_ > 8) + buffer_.push_back(EncodeByte(base::saturated_cast(bit_buf_ / 94))); + bit_count_ = 0; + bit_buf_ = 0; + return WriteOutputStream(); +} + +bool Base94OutputStream::FinishDecoding() { + // The left over bit is padding and all zero, if there is no symbol + // unprocessed. + if (symbol_buffer_ == 0) { + DCHECK(!bit_buf_); + return true; + } + bit_buf_ |= DecodeByte(symbol_buffer_) << bit_count_; + buffer_.push_back(bit_buf_ & 0xff); + bit_buf_ >>= 8; + // The remaining bits are either encode padding or zeros from bit shift. + DCHECK(!bit_buf_); + return WriteOutputStream(); +} + +bool Base94OutputStream::WriteOutputStream() { + if (buffer_.empty()) + return true; + + bool result = output_stream_->Write(buffer_.data(), buffer_.size()); + buffer_.clear(); + return result; +} + +} // namespace crashpad diff --git a/util/stream/base94_output_stream.h b/util/stream/base94_output_stream.h new file mode 100644 index 00000000..9d27b2ac --- /dev/null +++ b/util/stream/base94_output_stream.h @@ -0,0 +1,79 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_STREAM_BASE94_OUTPUT_STREAM_H_ +#define CRASHPAD_UTIL_STREAM_BASE94_OUTPUT_STREAM_H_ + +#include +#include + +#include +#include + +#include "base/macros.h" +#include "util/stream/output_stream_interface.h" + +namespace crashpad { + +//! \brief This class implements Base94 encoding/decoding, it uses all +//! printable characters except space for encoding, and no padding is required. +//! +//! This implementation uses two base94 symbols to encoding 13 or 14 bit data, +//! To maximize encoding efficiency, 14-bit data is encoded into two base94 +//! symbols if its low 13-bit is less than 644 ( = 94^2 - 2^13), otherwise +//! 13-bit data is encoded. +class Base94OutputStream : public OutputStreamInterface { + public: + //! \brief Whether this object is configured to encode or decode data. + enum class Mode : bool { + //! \brief Data passed through this object is encoded. + kEncode = false, + //! \brief Data passed through this object is decoded. + kDecode = true + }; + + //! \param[in] mode The work mode of this object. + //! \param[in] output_stream The output_stream that this object writes to. + Base94OutputStream(Mode mode, + std::unique_ptr output_stream); + ~Base94OutputStream() override; + + // OutputStreamInterface: + bool Write(const uint8_t* data, size_t size) override; + bool Flush() override; + + private: + bool Encode(const uint8_t* data, size_t size); + bool Decode(const uint8_t* data, size_t size); + bool FinishEncoding(); + bool FinishDecoding(); + // Write encoded/decoded data to |output_stream_| and empty the |buffer_|. + bool WriteOutputStream(); + + Mode mode_; + std::unique_ptr output_stream_; + std::vector buffer_; + uint32_t bit_buf_; + // The number of valid bit in bit_buf_. + size_t bit_count_; + char symbol_buffer_; + bool flush_needed_; + bool flushed_; + + DISALLOW_COPY_AND_ASSIGN(Base94OutputStream); +}; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_STREAM_BASE94_OUTPUT_STREAM_H_ diff --git a/util/stream/base94_output_stream_test.cc b/util/stream/base94_output_stream_test.cc new file mode 100644 index 00000000..32decbba --- /dev/null +++ b/util/stream/base94_output_stream_test.cc @@ -0,0 +1,290 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/stream/base94_output_stream.h" + +#include + +#include +#include + +#include "base/macros.h" +#include "base/rand_util.h" +#include "base/stl_util.h" +#include "base/strings/stringprintf.h" +#include "gtest/gtest.h" +#include "util/stream/test_output_stream.h" + +namespace crashpad { +namespace test { +namespace { + +constexpr size_t kLongDataLength = 4096 * 10; + +std::string DumpInput(const uint8_t* input, size_t size) { + std::stringstream s; + size_t index = 0; + size_t byte_count = 0; + while (index < size) { + s << "0x" << std::hex << static_cast(*(input + index++)) << ","; + if (byte_count++ > 1024) { + s << "\n"; + byte_count = 0; + } + } + return s.str(); +} + +class Base94OutputStreamTest : public testing::Test { + public: + Base94OutputStreamTest() {} + + protected: + void SetUp() override { + auto output_stream = std::make_unique(); + encode_test_output_stream_ = output_stream.get(); + encoder_ = std::make_unique( + Base94OutputStream::Mode::kEncode, std::move(output_stream)); + output_stream = std::make_unique(); + decode_test_output_stream_ = output_stream.get(); + decoder_ = std::make_unique( + Base94OutputStream::Mode::kDecode, std::move(output_stream)); + output_stream = std::make_unique(); + round_trip_test_output_stream_ = output_stream.get(); + round_trip_ = std::make_unique( + Base94OutputStream::Mode::kEncode, + std::make_unique(Base94OutputStream::Mode::kDecode, + std::move(output_stream))); + } + + const uint8_t* BuildDeterministicInput(size_t size) { + deterministic_input_ = std::make_unique(size); + uint8_t* deterministic_input_base = deterministic_input_.get(); + while (size-- > 0) + deterministic_input_base[size] = static_cast(size); + return deterministic_input_base; + } + + const uint8_t* BuildRandomInput(size_t size) { + input_ = std::make_unique(size); + base::RandBytes(&input_[0], size); + return input_.get(); + } + + Base94OutputStream* round_trip() const { return round_trip_.get(); } + const TestOutputStream& round_trip_test_output_stream() const { + return *round_trip_test_output_stream_; + } + + static void VerifyEncoding(const TestOutputStream& out, + const std::string& expected) { + EXPECT_EQ(out.all_data().size(), expected.size()); + EXPECT_EQ(memcmp(out.all_data().data(), expected.data(), expected.size()), + 0); + } + + static void VerifyDecoding(const TestOutputStream& out, + const std::vector& expected) { + EXPECT_EQ(out.all_data().size(), expected.size()); + EXPECT_EQ(memcmp(out.all_data().data(), expected.data(), expected.size()), + 0); + } + + void RunTest(const std::string& text, const std::vector& binary) { + EXPECT_TRUE(encoder_->Write(binary.data(), binary.size())); + EXPECT_TRUE(encoder_->Flush()); + VerifyEncoding(*encode_test_output_stream_, text); + EXPECT_TRUE(decoder_->Write(reinterpret_cast(text.data()), + text.size())); + EXPECT_TRUE(decoder_->Flush()); + VerifyDecoding(*decode_test_output_stream_, binary); + } + + void VerifyRoundTrip(const std::vector& expected) { + TestOutputStream* out = round_trip_test_output_stream_; + EXPECT_EQ(out->all_data().size(), expected.size()); + EXPECT_EQ(memcmp(out->all_data().data(), expected.data(), expected.size()), + 0); + } + + private: + std::unique_ptr encoder_; + std::unique_ptr decoder_; + std::unique_ptr round_trip_; + TestOutputStream* encode_test_output_stream_; + TestOutputStream* decode_test_output_stream_; + TestOutputStream* round_trip_test_output_stream_; + std::unique_ptr input_; + std::unique_ptr deterministic_input_; + + DISALLOW_COPY_AND_ASSIGN(Base94OutputStreamTest); +}; + +TEST_F(Base94OutputStreamTest, Encoding) { + std::vector binary = {0x0}; + std::string text("!"); + RunTest(text, binary); +} + +TEST_F(Base94OutputStreamTest, Encoding1) { + std::vector binary = {0x0, 0x0}; + std::string text("!!!"); + RunTest(text, binary); +} + +TEST_F(Base94OutputStreamTest, Encoding2) { + std::vector binary = {0x0, 0x0, 0x0}; + std::string text("!!!!"); + RunTest(text, binary); +} + +TEST_F(Base94OutputStreamTest, Encoding3) { + std::vector binary = {0x0, 0x0, 0x0, 0x0}; + std::string text("!!!!!"); + RunTest(text, binary); +} + +TEST_F(Base94OutputStreamTest, Encoding4) { + std::vector binary = {0x0, 0x0, 0x0, 0x0, 0x0}; + std::string text("!!!!!!"); + RunTest(text, binary); +} + +TEST_F(Base94OutputStreamTest, Encoding10) { + std::vector binary = {0xFF}; + std::string text("d#"); + RunTest(text, binary); +} + +TEST_F(Base94OutputStreamTest, Encoding11) { + std::vector binary = {0xFF, 0xFF}; + std::string text(".x("); + RunTest(text, binary); +} + +TEST_F(Base94OutputStreamTest, Encoding12) { + std::vector binary = {0xFF, 0xFF, 0xFF}; + std::string text(".xj6"); + RunTest(text, binary); +} + +TEST_F(Base94OutputStreamTest, Encoding13) { + std::vector binary = {0xFF, 0xFF, 0xFF, 0xFF}; + std::string text(".x.x`"); + RunTest(text, binary); +} + +TEST_F(Base94OutputStreamTest, Encoding14) { + std::vector binary = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + std::string text(".x.x.x\""); + RunTest(text, binary); +} + +TEST_F(Base94OutputStreamTest, Printable94) { + std::vector binary = { + 0x5e, 0x0, 0x47, 0xa0, 0x1d, 0x60, 0xa, 0xab, 0x41, 0x41, 0xa4, 0x9, + 0x64, 0x71, 0x32, 0xc, 0x47, 0xf9, 0x20, 0x22, 0xa3, 0x44, 0xa0, 0x84, + 0x15, 0xe0, 0xf2, 0x61, 0xfc, 0x4c, 0xb7, 0xe1, 0x39, 0x9b, 0x47, 0xff, + 0x64, 0x21, 0x5c, 0x74, 0x91, 0xec, 0x52, 0x75, 0xa2, 0x51, 0x93, 0x4a, + 0x5e, 0x45, 0x2d, 0xd8, 0xf5, 0xc0, 0xdc, 0x58, 0x33, 0x63, 0x69, 0x8b, + 0x4d, 0xbd, 0x25, 0x39, 0x54, 0x77, 0xf0, 0xcc, 0x5e, 0xf1, 0x23, 0x81, + 0x6, 0x21, 0x71, 0x28, 0x28, 0x2}; + std::string text( + "!\"#$%&'()*+,-./" + "0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`" + "abcdefghijklmnopqrstuvwxyz{|}~!"); + RunTest(text, binary); +} + +TEST_F(Base94OutputStreamTest, WriteLongDataMultipleTimes) { + const uint8_t* input = BuildRandomInput(kLongDataLength); + SCOPED_TRACE(base::StringPrintf("Input: %s", + DumpInput(input, kLongDataLength).c_str())); + // Call Write() a random number of times. + size_t index = 0; + while (index < kLongDataLength) { + size_t write_length = + std::min(static_cast(base::RandInt(0, 4096 * 2)), + kLongDataLength - index); + SCOPED_TRACE( + base::StringPrintf("index %zu, write_length %zu", index, write_length)); + EXPECT_TRUE(round_trip()->Write(input + index, write_length)); + index += write_length; + } + EXPECT_TRUE(round_trip()->Flush()); + EXPECT_EQ(round_trip_test_output_stream().all_data().size(), kLongDataLength); + EXPECT_EQ(memcmp(round_trip_test_output_stream().all_data().data(), + input, + kLongDataLength), + 0); +} + +TEST_F(Base94OutputStreamTest, WriteDeterministicLongDataMultipleTimes) { + const uint8_t* input = BuildDeterministicInput(kLongDataLength); + + static constexpr size_t kWriteLengths[] = { + 4, 96, 40, kLongDataLength - 4 - 96 - 40}; + + size_t offset = 0; + for (size_t index = 0; index < base::size(kWriteLengths); ++index) { + const size_t write_length = kWriteLengths[index]; + SCOPED_TRACE(base::StringPrintf( + "offset %zu, write_length %zu", offset, write_length)); + EXPECT_TRUE(round_trip()->Write(input + offset, write_length)); + offset += write_length; + } + EXPECT_TRUE(round_trip()->Flush()); + EXPECT_EQ(round_trip_test_output_stream().all_data().size(), kLongDataLength); + EXPECT_EQ(memcmp(round_trip_test_output_stream().all_data().data(), + input, + kLongDataLength), + 0); +} + +TEST_F(Base94OutputStreamTest, NoWriteOrFlush) { + EXPECT_EQ(round_trip_test_output_stream().write_count(), 0u); + EXPECT_EQ(round_trip_test_output_stream().flush_count(), 0u); + EXPECT_TRUE(round_trip_test_output_stream().all_data().empty()); +} + +TEST_F(Base94OutputStreamTest, FlushWithoutWrite) { + EXPECT_TRUE(round_trip()->Flush()); + EXPECT_EQ(round_trip_test_output_stream().write_count(), 0u); + EXPECT_EQ(round_trip_test_output_stream().flush_count(), 1u); + EXPECT_TRUE(round_trip_test_output_stream().all_data().empty()); +} + +TEST_F(Base94OutputStreamTest, WriteEmptyData) { + std::vector empty_data; + EXPECT_TRUE(round_trip()->Write( + static_cast(empty_data.data()), empty_data.size())); + EXPECT_TRUE(round_trip()->Flush()); + EXPECT_TRUE(round_trip()->Flush()); + EXPECT_EQ(round_trip_test_output_stream().write_count(), 0u); + EXPECT_EQ(round_trip_test_output_stream().flush_count(), 2u); + EXPECT_TRUE(round_trip_test_output_stream().all_data().empty()); +} + +TEST_F(Base94OutputStreamTest, Process7bitsInFinishDecoding) { + std::vector input = { + 0xff, 0xff, 0xff, 0xff, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + EXPECT_TRUE(round_trip()->Write(static_cast(input.data()), + input.size())); + EXPECT_TRUE(round_trip()->Flush()); + VerifyRoundTrip(input); +} + +} // namespace +} // namespace test +} // namespace crashpad diff --git a/util/stream/file_encoder.cc b/util/stream/file_encoder.cc new file mode 100644 index 00000000..bb2c41a3 --- /dev/null +++ b/util/stream/file_encoder.cc @@ -0,0 +1,85 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/stream/file_encoder.h" + +#include + +#include "base/logging.h" +#include "base/macros.h" +#include "util/file/file_io.h" +#include "util/file/file_reader.h" +#include "util/file/scoped_remove_file.h" +#include "util/stream/base94_output_stream.h" +#include "util/stream/file_output_stream.h" +#include "util/stream/output_stream_interface.h" +#include "util/stream/zlib_output_stream.h" + +namespace crashpad { + +FileEncoder::FileEncoder(Mode mode, + const base::FilePath& input_path, + const base::FilePath& output_path) + : mode_(mode), input_path_(input_path), output_path_(output_path) {} + +FileEncoder::~FileEncoder() {} + +bool FileEncoder::Process() { + ScopedRemoveFile file_remover; + ScopedFileHandle write_handle(LoggingOpenFileForWrite( + output_path_, FileWriteMode::kCreateOrFail, FilePermissions::kOwnerOnly)); + if (!write_handle.is_valid()) + return false; + + // Remove the output file on failure. + file_remover.reset(output_path_); + + std::unique_ptr output; + if (mode_ == Mode::kEncode) { + output = std::make_unique( + ZlibOutputStream::Mode::kCompress, + std::make_unique( + Base94OutputStream::Mode::kEncode, + std::make_unique(write_handle.get()))); + } else { + output = std::make_unique( + Base94OutputStream::Mode::kDecode, + std::make_unique( + ZlibOutputStream::Mode::kDecompress, + std::make_unique(write_handle.get()))); + } + + FileReader file_reader; + if (!file_reader.Open(input_path_)) + return false; + + FileOperationResult read_result; + do { + uint8_t buffer[4096]; + read_result = file_reader.Read(buffer, sizeof(buffer)); + if (read_result < 0) + return false; + + if (read_result > 0 && (!output->Write(buffer, read_result))) + return false; + } while (read_result > 0); + + if (!output->Flush()) + return false; + + ignore_result(file_remover.release()); + return true; +} + +} // namespace crashpad diff --git a/util/stream/file_encoder.h b/util/stream/file_encoder.h new file mode 100644 index 00000000..2f34b0bf --- /dev/null +++ b/util/stream/file_encoder.h @@ -0,0 +1,59 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_STREAM_FILE_ENCODER_H_ +#define CRASHPAD_UTIL_STREAM_FILE_ENCODER_H_ + +#include "base/files/file_path.h" +#include "base/macros.h" + +namespace crashpad { + +//! \brief The class is used to compress and base94-encode, or base94-decode +//! and decompress the given input file to the output file. +class FileEncoder { + public: + //! \brief Whether this object is configured to encode or decode data. + enum class Mode : bool { + //! \brief Data passed through this object is encoded. + kEncode = false, + //! \brief Data passed through this object is decoded. + kDecode = true + }; + + //! \param[in] mode The work mode of this object. + //! \param[in] input_path The input file that this object reads from. + //! \param[in] output_path The output file that this object writes to. + FileEncoder(Mode mode, + const base::FilePath& input_path, + const base::FilePath& output_path); + ~FileEncoder(); + + //! \brief Encode/decode the data from \a input_path_ file according work + //! \a mode, and write the result to \a output_path_ on success. + //! + //! \return `true` on success. + bool Process(); + + private: + Mode mode_; + base::FilePath input_path_; + base::FilePath output_path_; + + DISALLOW_COPY_AND_ASSIGN(FileEncoder); +}; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_STREAM_FILE_ENCODER_H_ diff --git a/util/stream/file_encoder_test.cc b/util/stream/file_encoder_test.cc new file mode 100644 index 00000000..ff989146 --- /dev/null +++ b/util/stream/file_encoder_test.cc @@ -0,0 +1,112 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/stream/file_encoder.h" + +#include +#include +#include + +#include "base/files/file_path.h" +#include "base/macros.h" +#include "gtest/gtest.h" +#include "test/scoped_temp_dir.h" +#include "util/file/file_io.h" +#include "util/stream/file_output_stream.h" + +namespace crashpad { +namespace test { +namespace { + +constexpr size_t kBufferSize = 4096; + +class FileEncoderTest : public testing::Test { + public: + FileEncoderTest() {} + + void Verify(size_t size) { + std::string contents; + ASSERT_TRUE(LoggingReadEntireFile(decoded_, &contents)); + ASSERT_EQ(contents.size(), size); + EXPECT_EQ(memcmp(deterministic_input_.get(), contents.data(), size), 0); + } + + const uint8_t* BuildDeterministicInput(size_t size) { + deterministic_input_ = std::make_unique(size); + uint8_t* deterministic_input_base = deterministic_input_.get(); + while (size-- > 0) + deterministic_input_base[size] = static_cast(size); + return deterministic_input_base; + } + + void GenerateOrigFile(size_t size) { + ScopedFileHandle write_handle(OpenFileForWrite( + orig_, FileWriteMode::kCreateOrFail, FilePermissions::kOwnerOnly)); + ASSERT_TRUE(write_handle.is_valid()); + FileOutputStream out(write_handle.get()); + const uint8_t* buf = BuildDeterministicInput(size); + while (size > 0) { + size_t m = std::min(kBufferSize, size); + ASSERT_TRUE(out.Write(buf, m)); + size -= m; + buf += m; + } + ASSERT_TRUE(out.Flush()); + } + + FileEncoder* encoder() const { return encoder_.get(); } + + FileEncoder* decoder() const { return decoder_.get(); } + + protected: + void SetUp() override { + temp_dir_ = std::make_unique(); + orig_ = base::FilePath(temp_dir_->path().Append(FILE_PATH_LITERAL("orig"))); + encoded_ = + base::FilePath(temp_dir_->path().Append(FILE_PATH_LITERAL("encoded"))); + decoded_ = + base::FilePath(temp_dir_->path().Append(FILE_PATH_LITERAL("decoded"))); + encoder_ = std::make_unique( + FileEncoder::Mode::kEncode, orig_, encoded_); + decoder_ = std::make_unique( + FileEncoder::Mode::kDecode, encoded_, decoded_); + } + + private: + std::unique_ptr temp_dir_; + base::FilePath orig_; + base::FilePath encoded_; + base::FilePath decoded_; + std::unique_ptr encoder_; + std::unique_ptr decoder_; + std::unique_ptr deterministic_input_; +}; + +TEST_F(FileEncoderTest, ProcessShortFile) { + GenerateOrigFile(kBufferSize - 512); + EXPECT_TRUE(encoder()->Process()); + EXPECT_TRUE(decoder()->Process()); + Verify(kBufferSize - 512); +} + +TEST_F(FileEncoderTest, ProcessLongFile) { + GenerateOrigFile(kBufferSize + 512); + EXPECT_TRUE(encoder()->Process()); + EXPECT_TRUE(decoder()->Process()); + Verify(kBufferSize + 512); +} + +} // namespace +} // namespace test +} // namespace crashpad diff --git a/util/stream/file_output_stream.cc b/util/stream/file_output_stream.cc new file mode 100644 index 00000000..022d91fe --- /dev/null +++ b/util/stream/file_output_stream.cc @@ -0,0 +1,45 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/stream/file_output_stream.h" + +#include "base/logging.h" + +namespace crashpad { + +FileOutputStream::FileOutputStream(FileHandle file_handle) + : writer_(file_handle), flush_needed_(false), flushed_(false) {} + +FileOutputStream::~FileOutputStream() { + DCHECK(!flush_needed_); +} + +bool FileOutputStream::Write(const uint8_t* data, size_t size) { + DCHECK(!flushed_); + + if (!writer_.Write(data, size)) { + LOG(ERROR) << "Write: Failed"; + return false; + } + flush_needed_ = true; + return true; +} + +bool FileOutputStream::Flush() { + flush_needed_ = false; + flushed_ = true; + return true; +} + +} // namespace crashpad diff --git a/util/stream/file_output_stream.h b/util/stream/file_output_stream.h new file mode 100644 index 00000000..128ccd5e --- /dev/null +++ b/util/stream/file_output_stream.h @@ -0,0 +1,46 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_STREAM_FILE_OUTPUT_STREAM_H_ +#define CRASHPAD_UTIL_STREAM_FILE_OUTPUT_STREAM_H_ + +#include "base/macros.h" +#include "util/file/file_io.h" +#include "util/file/file_writer.h" +#include "util/stream/output_stream_interface.h" + +namespace crashpad { + +//! \brief The class is used to write data to a file. +class FileOutputStream : public OutputStreamInterface { + public: + //! \param[in] file_handle The file that this object writes to. + explicit FileOutputStream(FileHandle file_handle); + ~FileOutputStream(); + + // OutputStream. + bool Write(const uint8_t* data, size_t size) override; + bool Flush() override; + + private: + WeakFileHandleFileWriter writer_; + bool flush_needed_; + bool flushed_; + + DISALLOW_COPY_AND_ASSIGN(FileOutputStream); +}; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_STREAM_FILE_OUTPUT_STREAM_H_ diff --git a/util/stream/log_output_stream.cc b/util/stream/log_output_stream.cc new file mode 100644 index 00000000..03c0a5a0 --- /dev/null +++ b/util/stream/log_output_stream.cc @@ -0,0 +1,130 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/stream/log_output_stream.h" + +#include + +#include "base/logging.h" + +#if defined(OS_ANDROID) +#include +#endif + +namespace crashpad { + +namespace { + +// Most minidumps are expected to be compressed and encoded into less than 128k. +constexpr size_t kOutputCap = 128 * 1024; + +// From Android NDK r20 , log message text may be truncated to +// less than an implementation-specific limit (1023 bytes), for sake of safe +// and being easy to read in logcat, choose 512. +constexpr size_t kLineBufferSize = 512; + +} // namespace + +LogOutputStream::LogOutputStream() + : output_count_(0), flush_needed_(false), flushed_(false) { + buffer_.reserve(kLineBufferSize); +} + +LogOutputStream::~LogOutputStream() { + DCHECK(!flush_needed_); +} + +bool LogOutputStream::Write(const uint8_t* data, size_t size) { + DCHECK(!flushed_); + flush_needed_ = true; + while (size > 0) { + size_t m = std::min(kLineBufferSize - buffer_.size(), size); + buffer_.append(reinterpret_cast(data), m); + data += m; + size -= m; + if (buffer_.size() == kLineBufferSize && !WriteBuffer()) { + flush_needed_ = false; + LOG(ERROR) << "Write: exceeds cap."; + if (output_stream_for_testing_) + output_stream_for_testing_->Flush(); + return false; + } + } + return true; +} + +bool LogOutputStream::WriteBuffer() { + if (output_count_ == 0) { + if (!WriteToLog("-----BEGIN CRASHPAD MINIDUMP-----")) + return false; + } + + if (buffer_.empty()) + return true; + + output_count_ += buffer_.size(); + if (output_count_ > kOutputCap) { + WriteToLog("-----ABORT CRASHPAD MINIDUMP-----"); + return false; + } + + bool result = WriteToLog(buffer_.c_str()); + buffer_.clear(); + return result; +} + +bool LogOutputStream::WriteToLog(const char* buf) { +#if defined(OS_ANDROID) + int ret = + __android_log_buf_write(LOG_ID_CRASH, ANDROID_LOG_FATAL, "crashpad", buf); + if (ret < 0) { + errno = -ret; + PLOG(ERROR) << "__android_log_buf_write"; + return false; + } +#endif + // For testing. + if (output_stream_for_testing_) { + return output_stream_for_testing_->Write( + reinterpret_cast(buf), strlen(buf)); + } + return true; +} + +bool LogOutputStream::Flush() { + flush_needed_ = false; + flushed_ = true; + + bool result = true; + if (WriteBuffer()) { + result = WriteToLog("-----END CRASHPAD MINIDUMP-----"); + } else { + LOG(ERROR) << "Flush: exceeds cap."; + result = false; + } + + // Since output_stream_for_testing_'s Write() method has been called, its + // Flush() shall always be invoked. + if (output_stream_for_testing_) + output_stream_for_testing_->Flush(); + + return result; +} + +void LogOutputStream::SetOutputStreamForTesting( + std::unique_ptr stream) { + output_stream_for_testing_ = std::move(stream); +} + +} // namespace crashpad diff --git a/util/stream/log_output_stream.h b/util/stream/log_output_stream.h new file mode 100644 index 00000000..4a91f5c1 --- /dev/null +++ b/util/stream/log_output_stream.h @@ -0,0 +1,61 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_STREAM_LOG_OUTPUT_STREAM_H_ +#define CRASHPAD_UTIL_STREAM_LOG_OUTPUT_STREAM_H_ + +#include +#include + +#include +#include + +#include "base/macros.h" +#include "util/stream/output_stream_interface.h" + +namespace crashpad { + +//! \brief This class output the received data to Android log, NOP in other +//! platform. +//! +//! To avoid overflowing Android log, total 128k log data is allowed, after +//! that cap, the output is aborted. +class LogOutputStream : public OutputStreamInterface { + public: + LogOutputStream(); + ~LogOutputStream() override; + + // OutputStreamInterface: + bool Write(const uint8_t* data, size_t size) override; + bool Flush() override; + + void SetOutputStreamForTesting(std::unique_ptr stream); + + private: + // Flush the |buffer_|, return false if kOutputCap meet. + bool WriteBuffer(); + bool WriteToLog(const char* buf); + + std::string buffer_; + size_t output_count_; + bool flush_needed_; + bool flushed_; + std::unique_ptr output_stream_for_testing_; + + DISALLOW_COPY_AND_ASSIGN(LogOutputStream); +}; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_STREAM_LOG_OUTPUT_STREAM_H_ diff --git a/util/stream/log_output_stream_test.cc b/util/stream/log_output_stream_test.cc new file mode 100644 index 00000000..510df764 --- /dev/null +++ b/util/stream/log_output_stream_test.cc @@ -0,0 +1,126 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/stream/log_output_stream.h" + +#include +#include +#include + +#include "base/macros.h" +#include "gtest/gtest.h" +#include "util/stream/test_output_stream.h" + +namespace crashpad { +namespace test { +namespace { + +constexpr size_t kOutputCap = 128 * 1024; +constexpr size_t kLineBufferSize = 512; +const char* kBeginGuard = "-----BEGIN CRASHPAD MINIDUMP-----"; +const char* kEndGuard = "-----END CRASHPAD MINIDUMP-----"; +const char* kAbortGuard = "-----ABORT CRASHPAD MINIDUMP-----"; + +std::string ConvertToString(const std::vector& src) { + return std::string(reinterpret_cast(src.data()), src.size()); +} + +class LogOutputStreamTest : public testing::Test { + public: + LogOutputStreamTest() : test_output_stream_(nullptr) {} + + protected: + void SetUp() override { + std::unique_ptr output_stream = + std::make_unique(); + test_output_stream_ = output_stream.get(); + log_stream_ = std::make_unique(); + log_stream_->SetOutputStreamForTesting(std::move(output_stream)); + } + + const uint8_t* BuildDeterministicInput(size_t size) { + deterministic_input_ = std::make_unique(size); + uint8_t* deterministic_input_base = deterministic_input_.get(); + while (size-- > 0) + deterministic_input_base[size] = static_cast('a'); + return deterministic_input_base; + } + + TestOutputStream* test_output_stream() const { return test_output_stream_; } + + LogOutputStream* log_stream() const { return log_stream_.get(); } + + private: + std::unique_ptr log_stream_; + TestOutputStream* test_output_stream_; + std::unique_ptr deterministic_input_; + + DISALLOW_COPY_AND_ASSIGN(LogOutputStreamTest); +}; + +TEST_F(LogOutputStreamTest, VerifyGuards) { + log_stream()->Flush(); + // Verify OutputStream wrote 2 guards. + EXPECT_EQ(test_output_stream()->write_count(), 2u); + EXPECT_EQ(test_output_stream()->flush_count(), 1u); + EXPECT_FALSE(test_output_stream()->all_data().empty()); + EXPECT_EQ(ConvertToString(test_output_stream()->all_data()), + std::string(kBeginGuard).append(kEndGuard)); +} + +TEST_F(LogOutputStreamTest, WriteShortLog) { + const uint8_t* input = BuildDeterministicInput(2); + EXPECT_TRUE(log_stream()->Write(input, 2)); + EXPECT_TRUE(log_stream()->Flush()); + // Verify OutputStream wrote 2 guards and data. + EXPECT_EQ(test_output_stream()->write_count(), 3u); + EXPECT_EQ(test_output_stream()->flush_count(), 1u); + EXPECT_FALSE(test_output_stream()->all_data().empty()); + EXPECT_EQ(ConvertToString(test_output_stream()->all_data()), + std::string(kBeginGuard).append("aa").append(kEndGuard)); +} + +TEST_F(LogOutputStreamTest, WriteLongLog) { + size_t input_length = kLineBufferSize + kLineBufferSize / 2; + const uint8_t* input = BuildDeterministicInput(input_length); + // Verify OutputStream wrote 2 guards and data. + EXPECT_TRUE(log_stream()->Write(input, input_length)); + EXPECT_TRUE(log_stream()->Flush()); + EXPECT_EQ(test_output_stream()->write_count(), + 2 + input_length / kLineBufferSize + 1); + EXPECT_EQ(test_output_stream()->flush_count(), 1u); + EXPECT_EQ(test_output_stream()->all_data().size(), + strlen(kBeginGuard) + strlen(kEndGuard) + input_length); +} + +TEST_F(LogOutputStreamTest, WriteAbort) { + size_t input_length = kOutputCap + kLineBufferSize; + const uint8_t* input = BuildDeterministicInput(input_length); + EXPECT_FALSE(log_stream()->Write(input, input_length)); + std::string data(ConvertToString(test_output_stream()->all_data())); + EXPECT_EQ(data.substr(data.size() - strlen(kAbortGuard)), kAbortGuard); +} + +TEST_F(LogOutputStreamTest, FlushAbort) { + size_t input_length = kOutputCap + kLineBufferSize / 2; + const uint8_t* input = BuildDeterministicInput(input_length); + EXPECT_TRUE(log_stream()->Write(input, input_length)); + EXPECT_FALSE(log_stream()->Flush()); + std::string data(ConvertToString(test_output_stream()->all_data())); + EXPECT_EQ(data.substr(data.size() - strlen(kAbortGuard)), kAbortGuard); +} + +} // namespace +} // namespace test +} // namespace crashpad diff --git a/util/stream/output_stream_interface.h b/util/stream/output_stream_interface.h new file mode 100644 index 00000000..397860ef --- /dev/null +++ b/util/stream/output_stream_interface.h @@ -0,0 +1,64 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_STREAM_OUTPUT_STREAM_INTERFACE_H_ +#define CRASHPAD_UTIL_STREAM_OUTPUT_STREAM_INTERFACE_H_ + +#include +#include + +namespace crashpad { + +//! \brief The interface for an output stream pipeline. +//! +//! Example: +//! +//! class OutputStreamInterfaceImpl : public OutputStreamInterface { +//! ... +//! }; +//! +//! // Create a OutputStream. +//! OutputStreamInterfaceImpl impl(...); +//! // Write the data multiple times. +//! while (has_data) { +//! impl.Write(data, size); +//! ... +//! } +//! // Flush internal buffer to indicate all data has been written. +//! impl.Flush(); +//! +//! +class OutputStreamInterface { + public: + virtual ~OutputStreamInterface() = default; + + //! \brief Writes \a data to this stream. This method may be called multiple + //! times for streaming. + //! + //! \param[in] data The data that should be written. + //! \param[in] size The size of \a data. + //! + //! \return `true` on success. + virtual bool Write(const uint8_t* data, size_t size) = 0; + + //! \brief Flush the internal buffer after all data has been written. + //! + //! Write() can't be called afterwards. + //! \return `true` on success. + virtual bool Flush() = 0; +}; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_STREAM_OUTPUT_STREAM_INTERFACE_H_ diff --git a/util/stream/test_output_stream.cc b/util/stream/test_output_stream.cc new file mode 100644 index 00000000..04ef7007 --- /dev/null +++ b/util/stream/test_output_stream.cc @@ -0,0 +1,48 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/stream/test_output_stream.h" +#include "base/logging.h" + +namespace crashpad { +namespace test { + +TestOutputStream::TestOutputStream() + : last_written_data_(), + all_data_(), + write_count_(0), + flush_count_(0), + flush_needed_(false) {} + +TestOutputStream::~TestOutputStream() { + DCHECK(!flush_needed_); +} + +bool TestOutputStream::Write(const uint8_t* data, size_t size) { + last_written_data_.assign(data, data + size); + all_data_.insert(all_data_.end(), data, data + size); + + flush_needed_ = true; + write_count_++; + return true; +} + +bool TestOutputStream::Flush() { + flush_needed_ = false; + flush_count_++; + return true; +} + +} // namespace test +} // namespace crashpad diff --git a/util/stream/test_output_stream.h b/util/stream/test_output_stream.h new file mode 100644 index 00000000..dcd0d45d --- /dev/null +++ b/util/stream/test_output_stream.h @@ -0,0 +1,66 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_STREAM_TEST_OUTPUT_STREAM_H_ +#define CRASHPAD_UTIL_STREAM_TEST_OUTPUT_STREAM_H_ + +#include +#include + +#include + +#include "base/macros.h" +#include "util/stream/output_stream_interface.h" + +namespace crashpad { +namespace test { + +//! \brief The help class for \a OutputStreamInterface related tests. +class TestOutputStream : public OutputStreamInterface { + public: + TestOutputStream(); + ~TestOutputStream() override; + + // OutputStreamInterface: + bool Write(const uint8_t* data, size_t size) override; + bool Flush() override; + + //! \return the data that has been received by the last call of Write(). + const std::vector& last_written_data() const { + return last_written_data_; + } + + //! \return all data that has been received. + const std::vector& all_data() const { return all_data_; } + + //! \return the number of times Write() has been called. + size_t write_count() const { return write_count_; } + + //! \return the number of times Flush() has been called. + size_t flush_count() const { return flush_count_; } + + private: + std::vector last_written_data_; + std::vector all_data_; + size_t write_count_; + size_t flush_count_; + bool flush_needed_; + + DISALLOW_COPY_AND_ASSIGN(TestOutputStream); +}; + +} // namespace test +} // namespace crashpad + +#endif // CRASHPAD_UTIL_STREAM_TEST_OUTPUT_STREAM_H_ diff --git a/util/stream/zlib_output_stream.cc b/util/stream/zlib_output_stream.cc new file mode 100644 index 00000000..88861db1 --- /dev/null +++ b/util/stream/zlib_output_stream.cc @@ -0,0 +1,139 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/stream/zlib_output_stream.h" + +#include "base/logging.h" +#include "base/numerics/safe_conversions.h" +#include "base/stl_util.h" +#include "util/misc/zlib.h" + +namespace crashpad { + +ZlibOutputStream::ZlibOutputStream( + Mode mode, + std::unique_ptr output_stream) + : output_stream_(std::move(output_stream)), + mode_(mode), + initialized_(), + flush_needed_(false) {} + +ZlibOutputStream::~ZlibOutputStream() { + if (!initialized_.is_valid()) + return; + DCHECK(!flush_needed_); + if (mode_ == Mode::kCompress) { + if (deflateEnd(&zlib_stream_) != Z_OK) + LOG(ERROR) << "deflateEnd: " << zlib_stream_.msg; + } else if (mode_ == Mode::kDecompress) { + if (inflateEnd(&zlib_stream_) != Z_OK) + LOG(ERROR) << "inflateEnd: " << zlib_stream_.msg; + } +} + +bool ZlibOutputStream::Write(const uint8_t* data, size_t size) { + if (initialized_.is_uninitialized()) { + initialized_.set_invalid(); + + zlib_stream_.zalloc = Z_NULL; + zlib_stream_.zfree = Z_NULL; + zlib_stream_.opaque = Z_NULL; + + if (mode_ == Mode::kDecompress) { + int result = inflateInit(&zlib_stream_); + if (result != Z_OK) { + LOG(ERROR) << "inflateInit: " << ZlibErrorString(result); + return false; + } + } else if (mode_ == Mode::kCompress) { + int result = deflateInit(&zlib_stream_, Z_BEST_COMPRESSION); + if (result != Z_OK) { + LOG(ERROR) << "deflateInit: " << ZlibErrorString(result); + return false; + } + } + zlib_stream_.next_out = buffer_; + zlib_stream_.avail_out = base::saturated_cast(base::size(buffer_)); + initialized_.set_valid(); + } + + if (!initialized_.is_valid()) + return false; + + zlib_stream_.next_in = data; + zlib_stream_.avail_in = base::saturated_cast(size); + flush_needed_ = false; + while (zlib_stream_.avail_in > 0) { + if (mode_ == Mode::kCompress) { + if (deflate(&zlib_stream_, Z_NO_FLUSH) != Z_OK) { + LOG(ERROR) << "deflate: " << zlib_stream_.msg; + return false; + } + } else if (mode_ == Mode::kDecompress) { + int result = inflate(&zlib_stream_, Z_NO_FLUSH); + if (result == Z_STREAM_END) { + if (zlib_stream_.avail_in > 0) { + LOG(ERROR) << "inflate: unconsumed input"; + return false; + } + } else if (result != Z_OK) { + LOG(ERROR) << "inflate: " << zlib_stream_.msg; + return false; + } + } + + if (!WriteOutputStream()) + return false; + } + flush_needed_ = true; + return true; +} + +bool ZlibOutputStream::Flush() { + if (initialized_.is_valid() && flush_needed_) { + flush_needed_ = false; + int result = Z_OK; + do { + if (mode_ == Mode::kCompress) { + result = deflate(&zlib_stream_, Z_FINISH); + if (result != Z_STREAM_END && result != Z_BUF_ERROR && result != Z_OK) { + LOG(ERROR) << "deflate: " << zlib_stream_.msg; + return false; + } + } else if (mode_ == Mode::kDecompress) { + result = inflate(&zlib_stream_, Z_FINISH); + if (result != Z_STREAM_END && result != Z_BUF_ERROR && result != Z_OK) { + LOG(ERROR) << "inflate: " << zlib_stream_.msg; + return false; + } + } + if (!WriteOutputStream()) + return false; + } while (result != Z_STREAM_END); + } + return output_stream_->Flush(); +} + +bool ZlibOutputStream::WriteOutputStream() { + auto valid_size = base::size(buffer_) - zlib_stream_.avail_out; + if (valid_size > 0 && !output_stream_->Write(buffer_, valid_size)) + return false; + + zlib_stream_.next_out = buffer_; + zlib_stream_.avail_out = base::saturated_cast(base::size(buffer_)); + + return true; +} + +} // namespace crashpad diff --git a/util/stream/zlib_output_stream.h b/util/stream/zlib_output_stream.h new file mode 100644 index 00000000..d8e62a6c --- /dev/null +++ b/util/stream/zlib_output_stream.h @@ -0,0 +1,82 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_STREAM_ZLIB_OUTPUT_STREAM_H_ +#define CRASHPAD_UTIL_STREAM_ZLIB_OUTPUT_STREAM_H_ + +#include +#include + +#include + +#include "base/macros.h" +#include "third_party/zlib/zlib_crashpad.h" +#include "util/misc/initialization_state.h" +#include "util/stream/output_stream_interface.h" + +namespace crashpad { + +//! \brief The class wraps zlib into \a OutputStreamInterface. +class ZlibOutputStream : public OutputStreamInterface { + public: + //! \brief Whether this object is configured to compress or decompress data. + enum class Mode : bool { + //! \brief Data passed through this object is compressed. + kCompress = false, + //! \brief Data passed through this object is decompressed. + kDecompress = true + }; + + //! \param[in] mode The work mode of this object. + //! \param[in] output_stream The output_stream that this object writes to. + //! + //! To construct an output pipeline, the output stream needs an output stream + //! to write the result to. For example, the code below constructs a + //! compress->base94-encoding->log output stream pipline. + //! + //! + //! ZlibOutputStream zlib_output_stream( + //! ZlibOutputStream::Mode::kDeflate, + //! std::make_unique( + //! Base94OutputStream::Mode::kEncode, + //! std::make_unique())); + //! + //! + //! + ZlibOutputStream(Mode mode, + std::unique_ptr output_stream); + ~ZlibOutputStream() override; + + // OutputStreamInterface: + bool Write(const uint8_t* data, size_t size) override; + bool Flush() override; + + private: + // Write compressed/decompressed data to |output_stream_| and empty the output + // buffer in |zlib_stream_|. + bool WriteOutputStream(); + + uint8_t buffer_[4096]; + z_stream zlib_stream_; + std::unique_ptr output_stream_; + Mode mode_; + InitializationState initialized_; // protects zlib_stream_ + bool flush_needed_; + + DISALLOW_COPY_AND_ASSIGN(ZlibOutputStream); +}; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_STREAM_ZLIB_OUTPUT_STREAM_H_ diff --git a/util/stream/zlib_output_stream_test.cc b/util/stream/zlib_output_stream_test.cc new file mode 100644 index 00000000..dfa935b6 --- /dev/null +++ b/util/stream/zlib_output_stream_test.cc @@ -0,0 +1,186 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/stream/zlib_output_stream.h" + +#include + +#include + +#include "base/rand_util.h" +#include "base/stl_util.h" +#include "base/strings/stringprintf.h" +#include "gtest/gtest.h" +#include "util/stream/test_output_stream.h" + +namespace crashpad { +namespace test { +namespace { + +constexpr size_t kShortDataLength = 10; +constexpr size_t kLongDataLength = 4096 * 10; + +class ZlibOutputStreamTest : public testing::Test { + public: + ZlibOutputStreamTest() : input_(), deterministic_input_() { + auto test_output_stream = std::make_unique(); + test_output_stream_ = test_output_stream.get(); + zlib_output_stream_ = std::make_unique( + ZlibOutputStream::Mode::kCompress, + std::make_unique(ZlibOutputStream::Mode::kDecompress, + std::move(test_output_stream))); + } + + const uint8_t* BuildDeterministicInput(size_t size) { + deterministic_input_ = std::make_unique(size); + uint8_t* deterministic_input_base = deterministic_input_.get(); + while (size-- > 0) + deterministic_input_base[size] = static_cast(size); + return deterministic_input_base; + } + + const uint8_t* BuildRandomInput(size_t size) { + input_ = std::make_unique(size); + base::RandBytes(&input_[0], size); + return input_.get(); + } + + const TestOutputStream& test_output_stream() const { + return *test_output_stream_; + } + + ZlibOutputStream* zlib_output_stream() const { + return zlib_output_stream_.get(); + } + + private: + std::unique_ptr zlib_output_stream_; + std::unique_ptr input_; + std::unique_ptr deterministic_input_; + TestOutputStream* test_output_stream_; // weak, owned by zlib_output_stream_ + + DISALLOW_COPY_AND_ASSIGN(ZlibOutputStreamTest); +}; + +TEST_F(ZlibOutputStreamTest, WriteDeterministicShortData) { + const uint8_t* input = BuildDeterministicInput(kShortDataLength); + EXPECT_TRUE(zlib_output_stream()->Write(input, kShortDataLength)); + EXPECT_TRUE(zlib_output_stream()->Flush()); + EXPECT_EQ(test_output_stream().last_written_data().size(), kShortDataLength); + EXPECT_EQ(memcmp(test_output_stream().last_written_data().data(), + input, + kShortDataLength), + 0); +} + +TEST_F(ZlibOutputStreamTest, WriteDeterministicLongDataOneTime) { + const uint8_t* input = BuildDeterministicInput(kLongDataLength); + EXPECT_TRUE(zlib_output_stream()->Write(input, kLongDataLength)); + EXPECT_TRUE(zlib_output_stream()->Flush()); + EXPECT_EQ(test_output_stream().all_data().size(), kLongDataLength); + EXPECT_EQ( + memcmp(test_output_stream().all_data().data(), input, kLongDataLength), + 0); +} + +TEST_F(ZlibOutputStreamTest, WriteDeterministicLongDataMultipleTimes) { + const uint8_t* input = BuildDeterministicInput(kLongDataLength); + + static constexpr size_t kWriteLengths[] = { + 4, 96, 40, kLongDataLength - 4 - 96 - 40}; + + size_t offset = 0; + for (size_t index = 0; index < base::size(kWriteLengths); ++index) { + const size_t write_length = kWriteLengths[index]; + SCOPED_TRACE(base::StringPrintf( + "offset %zu, write_length %zu", offset, write_length)); + EXPECT_TRUE(zlib_output_stream()->Write(input + offset, write_length)); + offset += write_length; + } + EXPECT_TRUE(zlib_output_stream()->Flush()); + EXPECT_EQ(test_output_stream().all_data().size(), kLongDataLength); + EXPECT_EQ( + memcmp(test_output_stream().all_data().data(), input, kLongDataLength), + 0); +} + +TEST_F(ZlibOutputStreamTest, WriteShortData) { + const uint8_t* input = BuildRandomInput(kShortDataLength); + EXPECT_TRUE(zlib_output_stream()->Write(input, kShortDataLength)); + EXPECT_TRUE(zlib_output_stream()->Flush()); + EXPECT_EQ(memcmp(test_output_stream().last_written_data().data(), + input, + kShortDataLength), + 0); + EXPECT_EQ(test_output_stream().last_written_data().size(), kShortDataLength); +} + +TEST_F(ZlibOutputStreamTest, WriteLongDataOneTime) { + const uint8_t* input = BuildRandomInput(kLongDataLength); + EXPECT_TRUE(zlib_output_stream()->Write(input, kLongDataLength)); + EXPECT_TRUE(zlib_output_stream()->Flush()); + EXPECT_EQ(test_output_stream().all_data().size(), kLongDataLength); + EXPECT_EQ( + memcmp(test_output_stream().all_data().data(), input, kLongDataLength), + 0); +} + +TEST_F(ZlibOutputStreamTest, WriteLongDataMultipleTimes) { + const uint8_t* input = BuildRandomInput(kLongDataLength); + + // Call Write() a random number of times. + size_t index = 0; + while (index < kLongDataLength) { + size_t write_length = + std::min(static_cast(base::RandInt(0, 4096 * 2)), + kLongDataLength - index); + SCOPED_TRACE( + base::StringPrintf("index %zu, write_length %zu", index, write_length)); + EXPECT_TRUE(zlib_output_stream()->Write(input + index, write_length)); + index += write_length; + } + EXPECT_TRUE(zlib_output_stream()->Flush()); + EXPECT_EQ(test_output_stream().all_data().size(), kLongDataLength); + EXPECT_EQ( + memcmp(test_output_stream().all_data().data(), input, kLongDataLength), + 0); +} + +TEST_F(ZlibOutputStreamTest, NoWriteOrFlush) { + EXPECT_EQ(test_output_stream().write_count(), 0u); + EXPECT_EQ(test_output_stream().flush_count(), 0u); + EXPECT_TRUE(test_output_stream().all_data().empty()); +} + +TEST_F(ZlibOutputStreamTest, FlushWithoutWrite) { + EXPECT_TRUE(zlib_output_stream()->Flush()); + EXPECT_EQ(test_output_stream().write_count(), 0u); + EXPECT_EQ(test_output_stream().flush_count(), 1u); + EXPECT_TRUE(test_output_stream().all_data().empty()); +} + +TEST_F(ZlibOutputStreamTest, WriteEmptyData) { + std::vector empty_data; + EXPECT_TRUE(zlib_output_stream()->Write( + static_cast(empty_data.data()), empty_data.size())); + EXPECT_TRUE(zlib_output_stream()->Flush()); + EXPECT_TRUE(zlib_output_stream()->Flush()); + EXPECT_EQ(test_output_stream().write_count(), 0u); + EXPECT_EQ(test_output_stream().flush_count(), 2u); + EXPECT_TRUE(test_output_stream().all_data().empty()); +} + +} // namespace +} // namespace test +} // namespace crashpad diff --git a/util/synchronization/semaphore_test.cc b/util/synchronization/semaphore_test.cc index 10f7546d..4f1c1cd5 100644 --- a/util/synchronization/semaphore_test.cc +++ b/util/synchronization/semaphore_test.cc @@ -16,7 +16,7 @@ #include -#include "base/macros.h" +#include "base/stl_util.h" #include "gtest/gtest.h" #if defined(OS_POSIX) @@ -126,7 +126,7 @@ TEST(Semaphore, TenThreaded) { Semaphore semaphore(5); ThreadMainInfo info[10]; size_t iterations = 0; - for (size_t index = 0; index < arraysize(info); ++index) { + for (size_t index = 0; index < base::size(info); ++index) { info[index].semaphore = &semaphore; info[index].iterations = index; iterations += info[index].iterations; @@ -138,7 +138,7 @@ TEST(Semaphore, TenThreaded) { semaphore.Signal(); } - for (size_t index = 0; index < arraysize(info); ++index) { + for (size_t index = 0; index < base::size(info); ++index) { JoinThread(&info[index]); } } diff --git a/util/thread/thread_log_messages.cc b/util/thread/thread_log_messages.cc index 60be08b5..60c82483 100644 --- a/util/thread/thread_log_messages.cc +++ b/util/thread/thread_log_messages.cc @@ -46,10 +46,6 @@ class ThreadLogMessagesMaster { private: ThreadLogMessagesMaster() { - DCHECK(!tls_.initialized()); - tls_.Initialize(nullptr); - DCHECK(tls_.initialized()); - DCHECK(!logging::GetLogMessageHandler()); logging::SetLogMessageHandler(LogMessageHandler); } @@ -62,7 +58,7 @@ class ThreadLogMessagesMaster { size_t message_start, const std::string& string) { std::vector* log_messages = - reinterpret_cast*>(tls_.Get()); + reinterpret_cast*>(GetInstance()->tls_.Get()); if (log_messages) { log_messages->push_back(string); } @@ -72,15 +68,11 @@ class ThreadLogMessagesMaster { return false; } - static base::ThreadLocalStorage::StaticSlot tls_; + base::ThreadLocalStorage::Slot tls_; DISALLOW_COPY_AND_ASSIGN(ThreadLogMessagesMaster); }; -// static -base::ThreadLocalStorage::StaticSlot ThreadLogMessagesMaster::tls_ - = TLS_INITIALIZER; - } // namespace ThreadLogMessages::ThreadLogMessages() : log_messages_() { diff --git a/util/thread/thread_log_messages_test.cc b/util/thread/thread_log_messages_test.cc index 00b612ce..94920e7f 100644 --- a/util/thread/thread_log_messages_test.cc +++ b/util/thread/thread_log_messages_test.cc @@ -18,6 +18,7 @@ #include #include "base/logging.h" +#include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "gtest/gtest.h" #include "util/thread/thread.h" @@ -93,8 +94,8 @@ TEST(ThreadLogMessages, Basic) { const std::vector& log_messages = thread_log_messages.log_messages(); - EXPECT_EQ(log_messages.size(), arraysize(kMessages)); - for (size_t index = 0; index < arraysize(kMessages); ++index) { + EXPECT_EQ(log_messages.size(), base::size(kMessages)); + for (size_t index = 0; index < base::size(kMessages); ++index) { EXPECT_EQ(MessageString(log_messages[index]), kMessages[index]) << "index " << index; } @@ -173,7 +174,7 @@ TEST(ThreadLogMessages, Multithreaded) { LoggingTestThread threads[20]; int start = 0; - for (size_t index = 0; index < arraysize(threads); ++index) { + for (size_t index = 0; index < base::size(threads); ++index) { threads[index].Initialize( index, static_cast(start), static_cast(index)); start += static_cast(index); diff --git a/util/thread/thread_test.cc b/util/thread/thread_test.cc index d92544b6..47a711c0 100644 --- a/util/thread/thread_test.cc +++ b/util/thread/thread_test.cc @@ -14,7 +14,6 @@ #include "util/thread/thread.h" -#include "base/macros.h" #include "gtest/gtest.h" #include "util/synchronization/semaphore.h" diff --git a/util/thread/worker_thread.cc b/util/thread/worker_thread.cc index 3cf48970..b5dcdb28 100644 --- a/util/thread/worker_thread.cc +++ b/util/thread/worker_thread.cc @@ -33,8 +33,9 @@ class WorkerThreadImpl final : public Thread { if (initial_work_delay_ > 0) semaphore_.TimedWait(initial_work_delay_); - while (self_->running_) { + while (self_->running_ || self_->do_work_now_) { self_->delegate_->DoWork(self_); + self_->do_work_now_ = false; semaphore_.TimedWait(self_->work_interval_); } } @@ -57,7 +58,8 @@ WorkerThread::WorkerThread(double work_interval, : work_interval_(work_interval), delegate_(delegate), impl_(), - running_(false) {} + running_(false), + do_work_now_(false) {} WorkerThread::~WorkerThread() { DCHECK(!running_); @@ -88,6 +90,7 @@ void WorkerThread::Stop() { void WorkerThread::DoWorkNow() { DCHECK(running_); + do_work_now_ = true; impl_->SignalSemaphore(); } diff --git a/util/thread/worker_thread.h b/util/thread/worker_thread.h index 97fb6ecd..0fae0090 100644 --- a/util/thread/worker_thread.h +++ b/util/thread/worker_thread.h @@ -92,6 +92,7 @@ class WorkerThread { Delegate* delegate_; // weak std::unique_ptr impl_; bool running_; + bool do_work_now_; DISALLOW_COPY_AND_ASSIGN(WorkerThread); }; diff --git a/util/util.gyp b/util/util.gyp index 1ffb752c..78a432a1 100644 --- a/util/util.gyp +++ b/util/util.gyp @@ -24,7 +24,9 @@ '../compat/compat.gyp:crashpad_compat', '../third_party/mini_chromium/mini_chromium.gyp:base', '../third_party/zlib/zlib.gyp:zlib', + '../third_party/lss/lss.gyp:lss', ], + 'defines': [ 'ZLIB_CONST' ], 'include_dirs': [ '..', '<(INTERMEDIATE_DIR)', @@ -48,6 +50,8 @@ 'file/filesystem_win.cc', 'file/file_writer.cc', 'file/file_writer.h', + 'file/output_stream_file_writer.cc', + 'file/output_stream_file_writer.h', 'file/scoped_remove_file.cc', 'file/scoped_remove_file.h', 'file/string_file.cc', @@ -63,10 +67,14 @@ 'linux/exception_handler_protocol.cc', 'linux/exception_handler_protocol.h', 'linux/exception_information.h', + 'linux/initial_signal_dispositions.cc', + 'linux/initial_signal_dispositions.h', 'linux/memory_map.cc', 'linux/memory_map.h', 'linux/proc_stat_reader.cc', 'linux/proc_stat_reader.h', + 'linux/proc_task_reader.cc', + 'linux/proc_task_reader.h', 'linux/ptrace_broker.cc', 'linux/ptrace_broker.h', 'linux/ptrace_client.cc', @@ -74,10 +82,14 @@ 'linux/ptrace_connection.h', 'linux/ptracer.cc', 'linux/ptracer.h', + 'linux/scoped_pr_set_dumpable.cc', + 'linux/scoped_pr_set_dumpable.h', 'linux/scoped_pr_set_ptracer.cc', 'linux/scoped_pr_set_ptracer.h', 'linux/scoped_ptrace_attach.cc', 'linux/scoped_ptrace_attach.h', + 'linux/socket.cc', + 'linux/socket.h', 'linux/thread_info.cc', 'linux/thread_info.h', 'linux/traits.h', @@ -122,11 +134,9 @@ 'mach/symbolic_constants_mach.h', 'mach/task_for_pid.cc', 'mach/task_for_pid.h', - 'mach/task_memory.cc', - 'mach/task_memory.h', 'misc/address_sanitizer.h', 'misc/address_types.h', - 'misc/arraysize_unsafe.h', + 'misc/arraysize.h', 'misc/as_underlying_type.h', 'misc/capture_context.h', 'misc/capture_context_linux.S', @@ -163,6 +173,7 @@ 'misc/symbolic_constants_common.h', 'misc/time.cc', 'misc/time.h', + 'misc/time_linux.cc', 'misc/time_win.cc', 'misc/tri_state.h', 'misc/uuid.cc', @@ -179,7 +190,6 @@ 'net/http_transport.cc', 'net/http_transport.h', 'net/http_transport_mac.mm', - 'net/http_transport_none.cc', 'net/http_transport_win.cc', 'net/url.cc', 'net/url.h', @@ -209,10 +219,13 @@ 'posix/signals.h', 'posix/symbolic_constants_posix.cc', 'posix/symbolic_constants_posix.h', + 'process/process_id.h', 'process/process_memory.cc', 'process/process_memory.h', 'process/process_memory_linux.cc', 'process/process_memory_linux.h', + 'process/process_memory_mac.cc', + 'process/process_memory_mac.h', 'process/process_memory_native.h', 'process/process_memory_range.cc', 'process/process_memory_range.h', @@ -227,6 +240,17 @@ 'stdlib/strnlen.cc', 'stdlib/strnlen.h', 'stdlib/thread_safe_vector.h', + 'stream/base94_output_stream.cc', + 'stream/base94_output_stream.h', + 'stream/file_encoder.cc', + 'stream/file_encoder.h', + 'stream/file_output_stream.cc', + 'stream/file_output_stream.h', + 'stream/log_output_stream.cc', + 'stream/log_output_stream.h', + 'stream/output_stream_interface.h', + 'stream/zlib_output_stream.cc', + 'stream/zlib_output_stream.h', 'string/split_string.cc', 'string/split_string.h', 'synchronization/semaphore_mac.cc', @@ -246,6 +270,7 @@ 'win/checked_win_address_range.h', 'win/command_line.cc', 'win/command_line.h', + 'win/context_wrappers.h', 'win/critical_section_with_debug_info.cc', 'win/critical_section_with_debug_info.h', 'win/exception_handler_server.cc', @@ -384,20 +409,24 @@ 'win/safe_terminate_process.asm', ], }], - ['OS=="linux"', { + ['OS=="android"', { + 'link_settings': { + 'libraries': [ + '-llog', + ], + }, + }], + ['OS=="linux" or OS=="android"', { 'sources': [ 'net/http_transport_socket.cc', + 'process/process_memory_sanitized.cc', + 'process/process_memory_sanitized.h', ], }, { # else: OS!="linux" 'sources!': [ 'misc/capture_context_linux.S', ], }], - ['OS!="android"', { - 'sources!': [ - 'net/http_transport_none.cc', - ], - }], ['OS!="linux" and OS!="android"', { 'sources/': [ ['exclude', '^process/'], @@ -410,10 +439,16 @@ ['include', '^linux/'], ['include', '^misc/capture_context_linux\\.S$'], ['include', '^misc/paths_linux\\.cc$'], + ['include', '^misc/time_linux\\.cc$'], ['include', '^posix/process_info_linux\\.cc$'], ['include', '^process/process_memory_linux\\.cc$'], ['include', '^process/process_memory_linux\\.h$'], ], + }, { # else: OS!="android" + 'sources!': [ + 'stream/log_output_stream.cc', + 'stream/log_output_stream.h', + ] }], ], }, diff --git a/util/util_test.gyp b/util/util_test.gyp index ff3559e3..f65a9186 100644 --- a/util/util_test.gyp +++ b/util/util_test.gyp @@ -28,6 +28,7 @@ '../test/test.gyp:crashpad_test', '../third_party/gtest/gmock.gyp:gmock', '../third_party/gtest/gtest.gyp:gtest', + '../third_party/lss/lss.gyp:lss', '../third_party/mini_chromium/mini_chromium.gyp:base', '../third_party/zlib/zlib.gyp:zlib', ], @@ -44,9 +45,11 @@ 'linux/auxiliary_vector_test.cc', 'linux/memory_map_test.cc', 'linux/proc_stat_reader_test.cc', + 'linux/proc_task_reader_test.cc', 'linux/ptrace_broker_test.cc', 'linux/ptracer_test.cc', 'linux/scoped_ptrace_attach_test.cc', + 'linux/socket_test.cc', 'mac/launchd_test.mm', 'mac/mac_util_test.mm', 'mac/service_management_test.mm', @@ -65,8 +68,7 @@ 'mach/notify_server_test.cc', 'mach/scoped_task_suspend_test.cc', 'mach/symbolic_constants_mach_test.cc', - 'mach/task_memory_test.cc', - 'misc/arraysize_unsafe_test.cc', + 'misc/arraysize_test.cc', 'misc/capture_context_test.cc', 'misc/capture_context_test_util.h', 'misc/capture_context_test_util_linux.cc', @@ -98,6 +100,7 @@ 'posix/scoped_mmap_test.cc', 'posix/signals_test.cc', 'posix/symbolic_constants_posix_test.cc', + 'process/process_memory_mac_test.cc', 'process/process_memory_range_test.cc', 'process/process_memory_test.cc', 'stdlib/aligned_allocator_test.cc', @@ -151,6 +154,11 @@ ['exclude', '^net/http_transport_test\\.cc$'], ] }], + ['OS=="linux" or OS=="android"', { + 'sources': [ + 'process/process_memory_sanitized_test.cc', + ], + }], ['OS!="linux" and OS!="android"', { 'sources/': [ ['exclude', '^process/'], @@ -193,6 +201,15 @@ 'cflags!': [ '-Wexit-time-destructors', ], + 'conditions': [ + ['OS=="win"', { + 'link_settings': { + 'libraries': [ + '-lws2_32.lib', + ], + }, + }], + ], }, ], }], diff --git a/util/win/command_line_test.cc b/util/win/command_line_test.cc index 025ef8a7..032d5555 100644 --- a/util/win/command_line_test.cc +++ b/util/win/command_line_test.cc @@ -19,8 +19,8 @@ #include #include "base/logging.h" -#include "base/macros.h" #include "base/scoped_generic.h" +#include "base/stl_util.h" #include "gtest/gtest.h" #include "test/errors.h" #include "util/win/scoped_local_alloc.h" @@ -65,7 +65,7 @@ TEST(CommandLine, AppendCommandLineArgument) { L"argument 1", L"argument 2", }; - AppendCommandLineArgumentTest(arraysize(kArguments), kArguments); + AppendCommandLineArgumentTest(base::size(kArguments), kArguments); } { @@ -77,7 +77,7 @@ TEST(CommandLine, AppendCommandLineArgument) { L"argument 2", L"\\some\\path with\\spaces", }; - AppendCommandLineArgumentTest(arraysize(kArguments), kArguments); + AppendCommandLineArgumentTest(base::size(kArguments), kArguments); } { @@ -89,7 +89,7 @@ TEST(CommandLine, AppendCommandLineArgument) { L"she said, \"you had me at hello\"", L"\\some\\path with\\spaces", }; - AppendCommandLineArgumentTest(arraysize(kArguments), kArguments); + AppendCommandLineArgumentTest(base::size(kArguments), kArguments); } { @@ -102,7 +102,7 @@ TEST(CommandLine, AppendCommandLineArgument) { L"argument3", L"argument4", }; - AppendCommandLineArgumentTest(arraysize(kArguments), kArguments); + AppendCommandLineArgumentTest(base::size(kArguments), kArguments); } { @@ -113,7 +113,7 @@ TEST(CommandLine, AppendCommandLineArgument) { L"\\some\\directory with\\spaces\\", L"argument2", }; - AppendCommandLineArgumentTest(arraysize(kArguments), kArguments); + AppendCommandLineArgumentTest(base::size(kArguments), kArguments); } { @@ -124,7 +124,7 @@ TEST(CommandLine, AppendCommandLineArgument) { L"", L"argument2", }; - AppendCommandLineArgumentTest(arraysize(kArguments), kArguments); + AppendCommandLineArgumentTest(base::size(kArguments), kArguments); } { @@ -159,7 +159,7 @@ TEST(CommandLine, AppendCommandLineArgument) { L"\"\"", L" \t\n\v\"", }; - AppendCommandLineArgumentTest(arraysize(kArguments), kArguments); + AppendCommandLineArgumentTest(base::size(kArguments), kArguments); } } diff --git a/util/win/context_wrappers.h b/util/win/context_wrappers.h new file mode 100644 index 00000000..5d9b1838 --- /dev/null +++ b/util/win/context_wrappers.h @@ -0,0 +1,40 @@ +// Copyright 2018 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_WIN_CONTEXT_WRAPPERS_H_ +#define CRASHPAD_UTIL_WIN_CONTEXT_WRAPPERS_H_ + +#include + +#include "build/build_config.h" + +namespace crashpad { + +//! \brief Retrieve program counter from `CONTEXT` structure for different +//! architectures supported by Windows. +inline void* ProgramCounterFromCONTEXT(const CONTEXT* context) { +#if defined(ARCH_CPU_X86) + return reinterpret_cast(context->Eip); +#elif defined(ARCH_CPU_X86_64) + return reinterpret_cast(context->Rip); +#elif defined(ARCH_CPU_ARM64) + return reinterpret_cast(context->Pc); +#else +#error Unsupported Windows Arch +#endif // ARCH_CPU_X86 +} + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_WIN_CONTEXT_WRAPPERS_H_ diff --git a/util/win/exception_handler_server.cc b/util/win/exception_handler_server.cc index 6642665c..c841f7bc 100644 --- a/util/win/exception_handler_server.cc +++ b/util/win/exception_handler_server.cc @@ -23,11 +23,9 @@ #include "base/logging.h" #include "base/numerics/safe_conversions.h" #include "base/rand_util.h" +#include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" -#include "minidump/minidump_file_writer.h" -#include "snapshot/crashpad_info_client_options.h" -#include "snapshot/win/process_snapshot_win.h" #include "util/file/file_writer.h" #include "util/misc/tri_state.h" #include "util/misc/uuid.h" @@ -307,7 +305,7 @@ void ExceptionHandlerServer::InitializeWithInheritedDataForInitialClient( void ExceptionHandlerServer::Run(Delegate* delegate) { uint64_t shutdown_token = base::RandUint64(); ScopedKernelHANDLE thread_handles[kPipeInstances]; - for (size_t i = 0; i < arraysize(thread_handles); ++i) { + for (size_t i = 0; i < base::size(thread_handles); ++i) { HANDLE pipe; if (first_pipe_instance_.is_valid()) { pipe = first_pipe_instance_.release(); @@ -359,7 +357,7 @@ void ExceptionHandlerServer::Run(Delegate* delegate) { } // Signal to the named pipe instances that they should terminate. - for (size_t i = 0; i < arraysize(thread_handles); ++i) { + for (size_t i = 0; i < base::size(thread_handles); ++i) { ClientToServerMessage message; memset(&message, 0, sizeof(message)); message.type = ClientToServerMessage::kShutdown; diff --git a/util/win/get_module_information.cc b/util/win/get_module_information.cc index 1a9fd0c4..850bfe12 100644 --- a/util/win/get_module_information.cc +++ b/util/win/get_module_information.cc @@ -22,9 +22,13 @@ BOOL CrashpadGetModuleInformation(HANDLE process, HMODULE module, MODULEINFO* module_info, DWORD cb) { +#if PSAPI_VERSION == 1 static const auto get_module_information = - GET_FUNCTION_REQUIRED(L"psapi.dll", GetModuleInformation); + GET_FUNCTION_REQUIRED(L"psapi.dll", GetModuleInformation); return get_module_information(process, module, module_info, cb); +#elif PSAPI_VERSION == 2 + return GetModuleInformation(process, module, module_info, cb); +#endif } } // namespace crashpad diff --git a/util/win/get_module_information.h b/util/win/get_module_information.h index c7d1cf17..8fce0350 100644 --- a/util/win/get_module_information.h +++ b/util/win/get_module_information.h @@ -17,7 +17,9 @@ #include -#define PSAPI_VERSION 1 +#ifndef PSAPI_VERSION +#define PSAPI_VERSION 2 +#endif #include namespace crashpad { diff --git a/util/win/loader_lock.cc b/util/win/loader_lock.cc new file mode 100644 index 00000000..187ef84e --- /dev/null +++ b/util/win/loader_lock.cc @@ -0,0 +1,52 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/win/loader_lock.h" + +#include + +#include "build/build_config.h" +#include "util/win/process_structs.h" + +namespace crashpad { + +namespace { + +#ifdef ARCH_CPU_64_BITS +using NativeTraits = process_types::internal::Traits64; +#else +using NativeTraits = process_types::internal::Traits32; +#endif // ARCH_CPU_64_BITS + +using PEB = process_types::PEB; +using TEB = process_types::TEB; +using RTL_CRITICAL_SECTION = process_types::RTL_CRITICAL_SECTION; + +TEB* GetTeb() { + return reinterpret_cast(NtCurrentTeb()); +} + +PEB* GetPeb() { + return reinterpret_cast(GetTeb()->ProcessEnvironmentBlock); +} + +} // namespace + +bool IsThreadInLoaderLock() { + RTL_CRITICAL_SECTION* loader_lock = + reinterpret_cast(GetPeb()->LoaderLock); + return loader_lock->OwningThread == GetTeb()->ClientId.UniqueThread; +} + +} // namespace crashpad diff --git a/util/fuchsia/system_exception_port_key.h b/util/win/loader_lock.h similarity index 56% rename from util/fuchsia/system_exception_port_key.h rename to util/win/loader_lock.h index 0bbae690..6c6b311f 100644 --- a/util/fuchsia/system_exception_port_key.h +++ b/util/win/loader_lock.h @@ -1,4 +1,4 @@ -// Copyright 2018 The Crashpad Authors. All rights reserved. +// Copyright 2019 The Crashpad Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,16 +12,14 @@ // 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_ +#ifndef CRASHPAD_UTIL_WIN_LOADER_LOCK_H_ +#define CRASHPAD_UTIL_WIN_LOADER_LOCK_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; +//! \return `true` if the current thread holds the loader lock. +bool IsThreadInLoaderLock(); } // namespace crashpad -#endif // CRASHPAD_UTIL_FUCHSIA_EXCEPTION_PORT_KEY_H_ +#endif // CRASHPAD_UTIL_WIN_LOADER_LOCK_H_ diff --git a/util/win/loader_lock_test.cc b/util/win/loader_lock_test.cc new file mode 100644 index 00000000..d33ea5ac --- /dev/null +++ b/util/win/loader_lock_test.cc @@ -0,0 +1,36 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/win/loader_lock.h" + +#include "gtest/gtest.h" +#include "util/win/get_function.h" + +extern "C" bool LoaderLockDetected(); + +namespace crashpad { +namespace test { +namespace { + +TEST(LoaderLock, Detected) { + EXPECT_FALSE(IsThreadInLoaderLock()); + auto* loader_lock_detected = GET_FUNCTION_REQUIRED( + L"crashpad_util_test_loader_lock_test.dll", LoaderLockDetected); + EXPECT_TRUE(loader_lock_detected()); + EXPECT_FALSE(IsThreadInLoaderLock()); +} + +} // namespace +} // namespace test +} // namespace crashpad diff --git a/util/win/loader_lock_test_dll.cc b/util/win/loader_lock_test_dll.cc new file mode 100644 index 00000000..b673ab3d --- /dev/null +++ b/util/win/loader_lock_test_dll.cc @@ -0,0 +1,41 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "util/win/loader_lock.h" + +namespace { + +bool g_loader_lock_detected = false; + +} // namespace + +extern "C" { + +__declspec(dllexport) bool LoaderLockDetected() { + return g_loader_lock_detected; +} + +} // extern "C" + +BOOL WINAPI DllMain(HINSTANCE, DWORD reason, LPVOID) { + switch (reason) { + case DLL_PROCESS_ATTACH: + g_loader_lock_detected = crashpad::IsThreadInLoaderLock(); + break; + } + + return TRUE; +} diff --git a/util/win/ntstatus_logging.cc b/util/win/ntstatus_logging.cc index 442e49f4..e9a9b61a 100644 --- a/util/win/ntstatus_logging.cc +++ b/util/win/ntstatus_logging.cc @@ -16,6 +16,7 @@ #include +#include "base/stl_util.h" #include "base/strings/stringprintf.h" namespace { @@ -29,7 +30,7 @@ std::string FormatNtstatus(DWORD ntstatus) { ntstatus, 0, msgbuf, - arraysize(msgbuf), + static_cast(base::size(msgbuf)), nullptr); if (len) { // Most system messages end in a period and a space. Remove the space if diff --git a/util/win/process_info.cc b/util/win/process_info.cc index cd6bcd3e..49a6f27f 100644 --- a/util/win/process_info.cc +++ b/util/win/process_info.cc @@ -224,7 +224,7 @@ bool GetProcessBasicInformation(HANDLE process, sizeof(wow64_peb_address), &bytes_returned); if (!NT_SUCCESS(status)) { - NTSTATUS_LOG(ERROR, status), "NtQueryInformationProcess"; + NTSTATUS_LOG(ERROR, status) << "NtQueryInformationProcess"; return false; } if (bytes_returned != sizeof(wow64_peb_address)) { @@ -513,8 +513,14 @@ bool ProcessInfo::Initialize(HANDLE process) { // distinguish between these two cases. SYSTEM_INFO system_info; GetSystemInfo(&system_info); - is_64_bit_ = - system_info.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64; + +#if defined(ARCH_CPU_X86_FAMILY) + constexpr uint16_t kNative64BitArchitecture = PROCESSOR_ARCHITECTURE_AMD64; +#elif defined(ARCH_CPU_ARM_FAMILY) + constexpr uint16_t kNative64BitArchitecture = PROCESSOR_ARCHITECTURE_ARM64; +#endif + + is_64_bit_ = system_info.wProcessorArchitecture == kNative64BitArchitecture; } #if defined(ARCH_CPU_32_BITS) @@ -565,12 +571,12 @@ bool ProcessInfo::IsWow64() const { return is_wow64_; } -pid_t ProcessInfo::ProcessID() const { +crashpad::ProcessID ProcessInfo::ProcessID() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return process_id_; } -pid_t ProcessInfo::ParentProcessID() const { +crashpad::ProcessID ProcessInfo::ParentProcessID() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return inherited_from_process_id_; } diff --git a/util/win/process_info.h b/util/win/process_info.h index 58921c56..afbe1462 100644 --- a/util/win/process_info.h +++ b/util/win/process_info.h @@ -24,6 +24,7 @@ #include "base/macros.h" #include "util/misc/initialization_state_dcheck.h" #include "util/numeric/checked_range.h" +#include "util/process/process_id.h" #include "util/stdlib/aligned_allocator.h" #include "util/win/address_types.h" @@ -105,10 +106,10 @@ class ProcessInfo { bool IsWow64() const; //! \return The target process's process ID. - pid_t ProcessID() const; + crashpad::ProcessID ProcessID() const; //! \return The target process's parent process ID. - pid_t ParentProcessID() const; + crashpad::ProcessID ParentProcessID() const; //! \return The command line from the target process's Process Environment //! Block. @@ -173,8 +174,8 @@ class ProcessInfo { // This function is best-effort under low memory conditions. std::vector BuildHandleVector(HANDLE process) const; - pid_t process_id_; - pid_t inherited_from_process_id_; + crashpad::ProcessID process_id_; + crashpad::ProcessID inherited_from_process_id_; HANDLE process_; std::wstring command_line_; WinVMAddress peb_address_; diff --git a/util/win/process_info_test.cc b/util/win/process_info_test.cc index c7abdb6f..a43358d4 100644 --- a/util/win/process_info_test.cc +++ b/util/win/process_info_test.cc @@ -26,7 +26,6 @@ #include "build/build_config.h" #include "gtest/gtest.h" #include "test/errors.h" -#include "test/gtest_disabled.h" #include "test/scoped_temp_dir.h" #include "test/test_paths.h" #include "test/win/child_launcher.h" @@ -38,6 +37,7 @@ #include "util/win/get_function.h" #include "util/win/handle.h" #include "util/win/scoped_handle.h" +#include "util/win/scoped_registry_key.h" namespace crashpad { namespace test { @@ -202,7 +202,7 @@ TEST(ProcessInfo, OtherProcess) { #if defined(ARCH_CPU_64_BITS) TEST(ProcessInfo, OtherProcessWOW64) { if (!TestPaths::Has32BitBuildArtifacts()) { - DISABLED_TEST(); + GTEST_SKIP(); } TestOtherProcess(TestPaths::Architecture::k32Bit); @@ -528,18 +528,6 @@ TEST(ProcessInfo, ReadableRanges) { &bytes_read)); } -struct ScopedRegistryKeyCloseTraits { - static HKEY InvalidValue() { - return nullptr; - } - static void Free(HKEY key) { - RegCloseKey(key); - } -}; - -using ScopedRegistryKey = - base::ScopedGeneric; - TEST(ProcessInfo, Handles) { ScopedTempDir temp_dir; diff --git a/util/win/registration_protocol_win.cc b/util/win/registration_protocol_win.cc index 4fb536d9..64ed518b 100644 --- a/util/win/registration_protocol_win.cc +++ b/util/win/registration_protocol_win.cc @@ -14,16 +14,63 @@ #include "util/win/registration_protocol_win.h" -#include #include +#include +#include +#include #include "base/logging.h" -#include "base/macros.h" +#include "base/stl_util.h" #include "util/win/exception_handler_server.h" +#include "util/win/loader_lock.h" #include "util/win/scoped_handle.h" +#include "util/win/scoped_local_alloc.h" namespace crashpad { +namespace { + +void* GetSecurityDescriptorWithUser(const base::char16* sddl_string, + size_t* size) { + if (size) + *size = 0; + + PSECURITY_DESCRIPTOR base_sec_desc; + if (!ConvertStringSecurityDescriptorToSecurityDescriptor( + sddl_string, SDDL_REVISION_1, &base_sec_desc, nullptr)) { + PLOG(ERROR) << "ConvertStringSecurityDescriptorToSecurityDescriptor"; + return nullptr; + } + + ScopedLocalAlloc base_sec_desc_owner(base_sec_desc); + EXPLICIT_ACCESS access; + wchar_t username[] = L"CURRENT_USER"; + BuildExplicitAccessWithName( + &access, username, GENERIC_ALL, GRANT_ACCESS, NO_INHERITANCE); + + PSECURITY_DESCRIPTOR user_sec_desc; + ULONG user_sec_desc_size; + DWORD error = BuildSecurityDescriptor(nullptr, + nullptr, + 1, + &access, + 0, + nullptr, + base_sec_desc, + &user_sec_desc_size, + &user_sec_desc); + if (error != ERROR_SUCCESS) { + SetLastError(error); + PLOG(ERROR) << "BuildSecurityDescriptor"; + return nullptr; + } + + *size = user_sec_desc_size; + return user_sec_desc; +} + +} // namespace + bool SendToCrashHandlerServer(const base::string16& pipe_name, const ClientToServerMessage& message, ServerToClientMessage* response) { @@ -124,16 +171,11 @@ HANDLE CreateNamedPipeInstance(const std::wstring& pipe_name, security_attributes_pointer); } -const void* GetSecurityDescriptorForNamedPipeInstance(size_t* size) { +const void* GetFallbackSecurityDescriptorForNamedPipeInstance(size_t* size) { // Mandatory Label, no ACE flags, no ObjectType, integrity level untrusted is - // "S:(ML;;;;;S-1-16-0)". Typically - // ConvertStringSecurityDescriptorToSecurityDescriptor() would be used to - // convert from a string representation. However, that function cannot be used - // because it is in advapi32.dll and CreateNamedPipeInstance() is called from - // within DllMain() where the loader lock is held. advapi32.dll is delay - // loaded in chrome_elf.dll because it must avoid loading user32.dll. If an - // advapi32.dll function were used, it would cause a load of the DLL, which - // would in turn cause deadlock. + // "S:(ML;;;;;S-1-16-0)". This static security descriptor is used as a + // fallback if GetSecurityDescriptorWithUser fails, to avoid losing crashes + // from non-AppContainer sandboxed applications. #pragma pack(push, 1) static constexpr struct SecurityDescriptorBlob { @@ -168,7 +210,8 @@ const void* GetSecurityDescriptorForNamedPipeInstance(size_t* size) { ACL_REVISION, // AclRevision. 0, // Sbz1. sizeof(kSecDescBlob.sacl), // AclSize. - arraysize(kSecDescBlob.sacl.ace), // AceCount. + static_cast( + base::size(kSecDescBlob.sacl.ace)), // AceCount. 0, // Sbz2. }, @@ -188,8 +231,9 @@ const void* GetSecurityDescriptorForNamedPipeInstance(size_t* size) { // sid. { SID_REVISION, // Revision. - // SubAuthorityCount. - arraysize(kSecDescBlob.sacl.ace[0].sid.SubAuthority), + // SubAuthorityCount. + static_cast(base::size( + kSecDescBlob.sacl.ace[0].sid.SubAuthority)), // IdentifierAuthority. {SECURITY_MANDATORY_LABEL_AUTHORITY}, {SECURITY_MANDATORY_UNTRUSTED_RID}, // SubAuthority. @@ -205,4 +249,23 @@ const void* GetSecurityDescriptorForNamedPipeInstance(size_t* size) { return reinterpret_cast(&kSecDescBlob); } +const void* GetSecurityDescriptorForNamedPipeInstance(size_t* size) { + CHECK(!IsThreadInLoaderLock()); + + // Get a security descriptor which grants the current user and SYSTEM full + // access to the named pipe. Also grant AppContainer RW access through the ALL + // APPLICATION PACKAGES SID (S-1-15-2-1). Finally add an Untrusted Mandatory + // Label for non-AppContainer sandboxed users. + static size_t sd_size; + static void* sec_desc = GetSecurityDescriptorWithUser( + L"D:(A;;GA;;;SY)(A;;GWGR;;;S-1-15-2-1)S:(ML;;;;;S-1-16-0)", &sd_size); + + if (!sec_desc) + return GetFallbackSecurityDescriptorForNamedPipeInstance(size); + + if (size) + *size = sd_size; + return sec_desc; +} + } // namespace crashpad diff --git a/util/win/registration_protocol_win.h b/util/win/registration_protocol_win.h index 5f04a466..ef8bebc1 100644 --- a/util/win/registration_protocol_win.h +++ b/util/win/registration_protocol_win.h @@ -145,10 +145,10 @@ bool SendToCrashHandlerServer(const base::string16& pipe_name, HANDLE CreateNamedPipeInstance(const std::wstring& pipe_name, bool first_instance); -//! \brief Returns the SECURITY_DESCRIPTOR blob that will be used for creating +//! \brief Returns the `SECURITY_DESCRIPTOR` blob that will be used for creating //! the connection pipe in CreateNamedPipeInstance(). //! -//! This function is exposed for only for testing. +//! This function is only exposed for testing. //! //! \param[out] size The size of the returned blob. May be `nullptr` if not //! required. @@ -157,6 +157,19 @@ HANDLE CreateNamedPipeInstance(const std::wstring& pipe_name, //! transferred to the caller. const void* GetSecurityDescriptorForNamedPipeInstance(size_t* size); +//! \brief Returns the `SECURITY_DESCRIPTOR` blob that will be used for creating +//! the connection pipe in CreateNamedPipeInstance() if the full descriptor +//! can't be created. +//! +//! This function is only exposed for testing. +//! +//! \param[out] size The size of the returned blob. May be `nullptr` if not +//! required. +//! +//! \return A pointer to a self-relative `SECURITY_DESCRIPTOR`. Ownership is not +//! transferred to the caller. +const void* GetFallbackSecurityDescriptorForNamedPipeInstance(size_t* size); + } // namespace crashpad #endif // CRASHPAD_UTIL_WIN_REGISTRATION_PROTOCOL_WIN_H_ diff --git a/util/win/registration_protocol_win_test.cc b/util/win/registration_protocol_win_test.cc index 6601b4e7..334d7d83 100644 --- a/util/win/registration_protocol_win_test.cc +++ b/util/win/registration_protocol_win_test.cc @@ -14,18 +14,122 @@ #include "util/win/registration_protocol_win.h" -#include +#include #include #include +#include +#include + +#include "base/logging.h" +#include "base/strings/string16.h" #include "gtest/gtest.h" #include "test/errors.h" +#include "util/win/scoped_handle.h" #include "util/win/scoped_local_alloc.h" namespace crashpad { namespace test { namespace { +base::string16 GetStringFromSid(PSID sid) { + LPWSTR sid_str; + if (!ConvertSidToStringSid(sid, &sid_str)) { + PLOG(ERROR) << "ConvertSidToStringSid"; + return base::string16(); + } + ScopedLocalAlloc sid_str_ptr(sid_str); + return sid_str; +} + +base::string16 GetUserSidString() { + HANDLE token_handle; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token_handle)) { + PLOG(ERROR) << "OpenProcessToken"; + return base::string16(); + } + + ScopedKernelHANDLE token(token_handle); + DWORD user_size = 0; + GetTokenInformation(token.get(), TokenUser, nullptr, 0, &user_size); + if (user_size == 0) { + PLOG(ERROR) << "GetTokenInformation Size"; + return base::string16(); + } + + std::vector user(user_size); + if (!GetTokenInformation( + token.get(), TokenUser, user.data(), user_size, &user_size)) { + PLOG(ERROR) << "GetTokenInformation"; + return base::string16(); + } + + TOKEN_USER* user_ptr = reinterpret_cast(user.data()); + return GetStringFromSid(user_ptr->User.Sid); +} + +void CheckAce(PACL acl, + DWORD index, + BYTE check_ace_type, + ACCESS_MASK check_mask, + const base::string16& check_sid) { + ASSERT_FALSE(check_sid.empty()); + void* ace_ptr; + ASSERT_TRUE(GetAce(acl, index, &ace_ptr)); + + ACE_HEADER* header = static_cast(ace_ptr); + ASSERT_EQ(check_ace_type, header->AceType); + ASSERT_EQ(0, header->AceFlags); + + PSID sid = nullptr; + ACCESS_MASK mask = 0; + switch (header->AceType) { + case ACCESS_ALLOWED_ACE_TYPE: { + ACCESS_ALLOWED_ACE* allowed_ace = + static_cast(ace_ptr); + sid = &allowed_ace->SidStart; + mask = allowed_ace->Mask; + } break; + case SYSTEM_MANDATORY_LABEL_ACE_TYPE: { + SYSTEM_MANDATORY_LABEL_ACE* label_ace = + static_cast(ace_ptr); + sid = &label_ace->SidStart; + mask = label_ace->Mask; + } break; + default: + NOTREACHED(); + break; + } + + ASSERT_EQ(check_mask, mask); + ASSERT_EQ(check_sid, GetStringFromSid(sid)); +} + +TEST(SecurityDescriptor, NamedPipeDefault) { + const void* sec_desc = GetSecurityDescriptorForNamedPipeInstance(nullptr); + + PACL acl; + BOOL acl_present; + BOOL acl_defaulted; + ASSERT_TRUE(GetSecurityDescriptorDacl( + const_cast(sec_desc), &acl_present, &acl, &acl_defaulted)); + ASSERT_EQ(3, acl->AceCount); + CheckAce(acl, 0, ACCESS_ALLOWED_ACE_TYPE, GENERIC_ALL, GetUserSidString()); + // Check SYSTEM user SID. + CheckAce(acl, 1, ACCESS_ALLOWED_ACE_TYPE, GENERIC_ALL, L"S-1-5-18"); + // Check ALL APPLICATION PACKAGES group SID. + CheckAce(acl, + 2, + ACCESS_ALLOWED_ACE_TYPE, + GENERIC_READ | GENERIC_WRITE, + L"S-1-15-2-1"); + + ASSERT_TRUE(GetSecurityDescriptorSacl( + const_cast(sec_desc), &acl_present, &acl, &acl_defaulted)); + ASSERT_EQ(1, acl->AceCount); + CheckAce(acl, 0, SYSTEM_MANDATORY_LABEL_ACE_TYPE, 0, L"S-1-16-0"); +} + TEST(SecurityDescriptor, MatchesAdvapi32) { // This security descriptor is built manually in the connection code to avoid // calling the advapi32 functions. Verify that it returns the same thing as @@ -43,7 +147,7 @@ TEST(SecurityDescriptor, MatchesAdvapi32) { size_t created_len; const void* const created = - GetSecurityDescriptorForNamedPipeInstance(&created_len); + GetFallbackSecurityDescriptorForNamedPipeInstance(&created_len); ASSERT_EQ(created_len, sec_desc_len); EXPECT_EQ(memcmp(sec_desc, created, sec_desc_len), 0); } diff --git a/util/win/safe_terminate_process_test.cc b/util/win/safe_terminate_process_test.cc index 3aba1591..d2e4b6dd 100644 --- a/util/win/safe_terminate_process_test.cc +++ b/util/win/safe_terminate_process_test.cc @@ -22,6 +22,7 @@ #include "base/files/file_path.h" #include "base/logging.h" #include "base/macros.h" +#include "base/stl_util.h" #include "build/build_config.h" #include "gtest/gtest.h" #include "test/errors.h" @@ -91,6 +92,8 @@ class ScopedExecutablePatch { DISALLOW_COPY_AND_ASSIGN(ScopedExecutablePatch); }; +// SafeTerminateProcess is calling convention specific only for x86. +#if defined(ARCH_CPU_X86_FAMILY) TEST(SafeTerminateProcess, PatchBadly) { // This is a test of SafeTerminateProcess(), but it doesn’t actually terminate // anything. Instead, it works with a process handle for the current process @@ -146,7 +149,7 @@ TEST(SafeTerminateProcess, PatchBadly) { }; void* target = reinterpret_cast(TerminateProcess); - ScopedExecutablePatch executable_patch(target, patch, arraysize(patch)); + ScopedExecutablePatch executable_patch(target, patch, base::size(patch)); // Make sure that SafeTerminateProcess() can be called. Since it’s been // patched with a no-op stub, GetLastError() shouldn’t be modified. @@ -161,6 +164,7 @@ TEST(SafeTerminateProcess, PatchBadly) { EXPECT_FALSE(SafeTerminateProcess(process, 0)); EXPECT_EQ(GetLastError(), static_cast(ERROR_ACCESS_DENIED)); } +#endif // ARCH_CPU_X86_FAMILY TEST(SafeTerminateProcess, TerminateChild) { base::FilePath child_executable = diff --git a/util/win/scoped_registry_key.h b/util/win/scoped_registry_key.h new file mode 100644 index 00000000..4393dc20 --- /dev/null +++ b/util/win/scoped_registry_key.h @@ -0,0 +1,34 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_WIN_SCOPED_REGISTRY_KEY_H_ +#define CRASHPAD_UTIL_WIN_SCOPED_REGISTRY_KEY_H_ + +#include + +#include "base/scoped_generic.h" + +namespace crashpad { + +struct ScopedRegistryKeyCloseTraits { + static HKEY InvalidValue() { return nullptr; } + static void Free(HKEY key) { RegCloseKey(key); } +}; + +using ScopedRegistryKey = + base::ScopedGeneric; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_WIN_SCOPED_REGISTRY_KEY_H_ \ No newline at end of file