Merge master bac601e785fc into doc

This commit is contained in:
Mark Mentovai 2020-04-28 09:36:02 -04:00
commit 7ac57464ca
584 changed files with 31694 additions and 7138 deletions

59
.gitattributes vendored Normal file
View File

@ -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

16
.gitignore vendored
View File

@ -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 *.Makefile
*.ninja *.ninja
*.pyc *.pyc
@ -10,6 +24,7 @@
.gdbinit .gdbinit
/Makefile /Makefile
/out /out
/third_party/edo/edo
/third_party/fuchsia/.cipd /third_party/fuchsia/.cipd
/third_party/fuchsia/clang /third_party/fuchsia/clang
/third_party/fuchsia/qemu /third_party/fuchsia/qemu
@ -19,6 +34,7 @@
/third_party/linux/.cipd /third_party/linux/.cipd
/third_party/linux/clang /third_party/linux/clang
/third_party/linux/sysroot /third_party/linux/sysroot
/third_party/lss/lss
/third_party/gyp/gyp /third_party/gyp/gyp
/third_party/mini_chromium/mini_chromium /third_party/mini_chromium/mini_chromium
/third_party/zlib/zlib /third_party/zlib/zlib

16
.style.yapf Normal file
View File

@ -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

32
.vpython Normal file
View File

@ -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"
>
>

127
BUILD.gn
View File

@ -14,29 +14,71 @@
import("build/crashpad_buildconfig.gni") import("build/crashpad_buildconfig.gni")
import("build/test.gni") import("build/test.gni")
import("util/net/tls.gni")
config("crashpad_config") { config("crashpad_config") {
include_dirs = [ "." ] 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) { if (crashpad_is_in_chromium || crashpad_is_in_fuchsia) {
test("crashpad_tests") { test("crashpad_tests") {
deps = [ deps = [
"client:client_test", "client:client_test",
"handler:handler_test",
"minidump:minidump_test", "minidump:minidump_test",
"snapshot:snapshot_test",
"test:gmock_main", "test:gmock_main",
"test:test_test", "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) { if (crashpad_is_in_fuchsia) {
import("//build/package.gni") import("//build/package.gni")
package("crashpad_test") { package("crashpad_test") {
testonly = true testonly = true
deprecated_system_image = true
deps = [ deps = [
":crashpad_tests", ":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_large",
"snapshot:crashpad_snapshot_test_module_small", "snapshot:crashpad_snapshot_test_module_small",
"test:crashpad_test_test_multiprocess_exec_test_child", "test:crashpad_test_test_multiprocess_exec_test_child",
"util:generate_test_server_key",
"util:http_transport_test_server", "util:http_transport_test_server",
] ]
@ -53,24 +94,23 @@ if (crashpad_is_in_chromium || crashpad_is_in_fuchsia) {
{ {
name = "crashpad_tests" name = "crashpad_tests"
}, },
]
meta = [
{
path = "test/fuchsia_crashpad_tests.cmx"
dest = "crashpad_tests.cmx"
},
]
binaries = [
{ {
name = "crashpad_test_test_multiprocess_exec_test_child" 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" name = "http_transport_test_server"
dest = "crashpad_test_data/http_transport_test_server" dest = "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"
}, },
] ]
@ -92,43 +132,39 @@ if (crashpad_is_in_chromium || crashpad_is_in_fuchsia) {
resources = [ resources = [
{ {
path = "util/net/testdata/ascii_http_body.txt" 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" 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" 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"
},
]
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_handler") {
deprecated_system_image = true
deps = [
"handler:crashpad_handler",
]
binaries = [
{
name = "crashpad_handler"
},
]
} }
package("crashpad_database_util") { package("crashpad_database_util") {
deprecated_system_image = true deps = [ "tools:crashpad_database_util" ]
deps = [
"tools:crashpad_database_util",
]
binaries = [ binaries = [
{ {
name = "crashpad_database_util" name = "crashpad_database_util"
shell = true
}, },
] ]
} }
@ -146,6 +182,9 @@ if (crashpad_is_in_chromium || crashpad_is_in_fuchsia) {
"handler:handler_test", "handler:handler_test",
"test:gtest_main", "test:gtest_main",
] ]
if (crashpad_is_ios || crashpad_is_fuchsia) {
deps -= [ "handler:handler_test" ]
}
} }
test("crashpad_minidump_test") { test("crashpad_minidump_test") {
@ -160,6 +199,9 @@ if (crashpad_is_in_chromium || crashpad_is_in_fuchsia) {
"snapshot:snapshot_test", "snapshot:snapshot_test",
"test:gtest_main", "test:gtest_main",
] ]
if (crashpad_is_ios) {
deps -= [ "snapshot:snapshot_test" ]
}
} }
test("crashpad_test_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" ]
}
}

View File

@ -12,3 +12,4 @@
Mark Mentovai <mark@chromium.org> Mark Mentovai <mark@chromium.org>
Robert Sesek <rsesek@chromium.org> Robert Sesek <rsesek@chromium.org>
Scott Graham <scottmg@chromium.org> Scott Graham <scottmg@chromium.org>
Joshua Peraza <jperaza@chromium.org>

99
DEPS
View File

@ -15,22 +15,34 @@
vars = { vars = {
'chromium_git': 'https://chromium.googlesource.com', 'chromium_git': 'https://chromium.googlesource.com',
'pull_linux_clang': False, '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 = { deps = {
'buildtools': 'buildtools':
Var('chromium_git') + '/chromium/buildtools.git@' + Var('chromium_git') + '/chromium/src/buildtools.git@' +
'6fe4a3251488f7af86d64fc25cf442e817cf6133', '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': 'crashpad/third_party/gtest/gtest':
Var('chromium_git') + '/external/github.com/google/googletest@' + Var('chromium_git') + '/external/github.com/google/googletest@' +
'c091b0469ab4c04ee9411ef770f32360945f4c53', 'e3f0319d89f4cbf32993de595d984183b1a9fc57',
'crashpad/third_party/gyp/gyp': 'crashpad/third_party/gyp/gyp':
Var('chromium_git') + '/external/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': 'crashpad/third_party/mini_chromium/mini_chromium':
Var('chromium_git') + '/chromium/mini_chromium@' + Var('chromium_git') + '/chromium/mini_chromium@' +
'793e94e2c652831af2d25bb5288b04e59048c62d', '8ca5ea356cdb97913d62d379d503567a80d90726',
'crashpad/third_party/libfuzzer/src': 'crashpad/third_party/libfuzzer/src':
Var('chromium_git') + '/chromium/llvm-project/compiler-rt/lib/fuzzer.git@' + Var('chromium_git') + '/chromium/llvm-project/compiler-rt/lib/fuzzer.git@' +
'fda403cf93ecb8792cb1d061564d89a6553ca020', 'fda403cf93ecb8792cb1d061564d89a6553ca020',
@ -49,7 +61,7 @@ deps = {
'condition': 'checkout_linux and pull_linux_clang', 'condition': 'checkout_linux and pull_linux_clang',
'dep_type': 'cipd' 'dep_type': 'cipd'
}, },
'crashpad/third_party/linux/clang/mac-amd64': { 'crashpad/third_party/fuchsia/clang/mac-amd64': {
'packages': [ 'packages': [
{ {
'package': 'fuchsia/clang/mac-amd64', 'package': 'fuchsia/clang/mac-amd64',
@ -89,22 +101,24 @@ deps = {
'condition': 'checkout_fuchsia and host_os == "linux"', 'condition': 'checkout_fuchsia and host_os == "linux"',
'dep_type': 'cipd' 'dep_type': 'cipd'
}, },
'crashpad/third_party/fuchsia/sdk/linux-amd64': { 'crashpad/third_party/fuchsia/sdk/mac-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).
'packages': [ 'packages': [
{ {
'package': 'fuchsia/sdk/linux-amd64', 'package': 'fuchsia/sdk/gn/mac-amd64',
'version': 'latest' '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' 'dep_type': 'cipd'
}, },
'crashpad/third_party/win/toolchain': { 'crashpad/third_party/win/toolchain': {
@ -163,45 +177,6 @@ hooks = [
'buildtools/win/clang-format.exe.sha1', '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 # If using a local clang ("pull_linux_clang" above), also pull down a
# sysroot. # sysroot.
@ -213,9 +188,13 @@ hooks = [
], ],
}, },
{ {
'name': 'gyp', 'name': 'setup_gn_ios',
'pattern': '\.gypi?$', 'pattern': '.',
'action': ['python', 'crashpad/build/gyp_crashpad.py'], 'condition': 'run_setup_ios_gn and checkout_ios',
'action': [
'python',
'crashpad/build/ios/setup_ios_gn.py'
],
}, },
] ]

View File

@ -36,7 +36,7 @@ https://chromium.googlesource.com/crashpad/crashpad.
* Bugs can be reported at the [Crashpad issue * Bugs can be reported at the [Crashpad issue
tracker](https://crashpad.chromium.org/bug/). 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. perform automated builds and tests.
* [crashpad-dev](https://groups.google.com/a/chromium.org/group/crashpad-dev) * [crashpad-dev](https://groups.google.com/a/chromium.org/group/crashpad-dev)
is the Crashpad developers mailing list. is the Crashpad developers mailing list.

View File

@ -32,9 +32,7 @@ config("crashpad_is_in_fuchsia") {
group("default_exe_manifest_win") { group("default_exe_manifest_win") {
if (crashpad_is_in_chromium) { if (crashpad_is_in_chromium) {
deps = [ deps = [ "//build/win:default_exe_manifest" ]
"//build/win:default_exe_manifest",
]
} }
} }
@ -45,7 +43,26 @@ config("crashpad_fuzzer_flags") {
"-fsanitize=fuzzer", "-fsanitize=fuzzer",
] ]
ldflags = [ ldflags = [ "-fsanitize=address" ]
"-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",
]
}
}
} }

View File

@ -55,6 +55,7 @@ declare_args() {
_default_configs = [ _default_configs = [
"//third_party/mini_chromium/mini_chromium/build:default", "//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:Wexit_time_destructors",
"//third_party/mini_chromium/mini_chromium/build:Wimplicit_fallthrough",
] ]
if (crashpad_use_libfuzzer) { if (crashpad_use_libfuzzer) {

View File

@ -35,6 +35,11 @@
}], }],
], ],
}], }],
['OS=="android"', {
'ldflags': [
'-static-libstdc++',
],
}],
], ],
}, },
} }

View File

@ -15,28 +15,28 @@
declare_args() { declare_args() {
# Determines various flavors of build configuration, and which concrete # Determines various flavors of build configuration, and which concrete
# targets to use for dependencies. Valid values are "standalone", "chromium", # targets to use for dependencies. Valid values are "standalone", "chromium",
# and "fuchsia". # "fuchsia", "dart" or "external".
crashpad_dependencies = "standalone" crashpad_dependencies = "standalone"
if (defined(is_fuchsia_tree) && is_fuchsia_tree) { 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" crashpad_dependencies = "fuchsia"
} }
} }
assert( assert(
crashpad_dependencies == "chromium" || crashpad_dependencies == "fuchsia" || 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_chromium = crashpad_dependencies == "chromium"
crashpad_is_in_fuchsia = crashpad_dependencies == "fuchsia" 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" crashpad_is_standalone = crashpad_dependencies == "standalone"
if (crashpad_is_in_chromium) { if (crashpad_is_in_chromium) {
crashpad_is_mac = is_mac crashpad_is_mac = is_mac
crashpad_is_ios = is_ios
crashpad_is_win = is_win crashpad_is_win = is_win
crashpad_is_linux = is_linux crashpad_is_linux = is_linux
crashpad_is_android = is_android crashpad_is_android = is_android
@ -46,11 +46,18 @@ if (crashpad_is_in_chromium) {
crashpad_is_clang = is_clang crashpad_is_clang = is_clang
} else { } else {
# 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 # Both standalone and in Fuchsia tree use mini_chromium, and it's mapped into
# the same location in both cases. # the same location in both cases.
import("../third_party/mini_chromium/mini_chromium/build/compiler.gni") import("../third_party/mini_chromium/mini_chromium/build/compiler.gni")
import("../third_party/mini_chromium/mini_chromium/build/platform.gni") import("../third_party/mini_chromium/mini_chromium/build/platform.gni")
}
crashpad_is_mac = mini_chromium_is_mac crashpad_is_mac = mini_chromium_is_mac
crashpad_is_ios = mini_chromium_is_ios
crashpad_is_win = mini_chromium_is_win crashpad_is_win = mini_chromium_is_win
crashpad_is_linux = mini_chromium_is_linux crashpad_is_linux = mini_chromium_is_linux
crashpad_is_android = mini_chromium_is_android crashpad_is_android = mini_chromium_is_android
@ -63,7 +70,12 @@ if (crashpad_is_in_chromium) {
template("crashpad_executable") { template("crashpad_executable") {
executable(target_name) { executable(target_name) {
forward_variables_from(invoker, "*", [ "configs", "remove_configs" ]) forward_variables_from(invoker,
"*",
[
"configs",
"remove_configs",
])
if (defined(invoker.remove_configs)) { if (defined(invoker.remove_configs)) {
configs -= invoker.remove_configs configs -= invoker.remove_configs
} }
@ -72,21 +84,23 @@ template("crashpad_executable") {
configs += invoker.configs configs += invoker.configs
} }
if (!defined(deps)) { if (crashpad_is_in_fuchsia) {
deps = [] fdio_config = [ "//build/config/fuchsia:fdio_config" ]
if (configs + fdio_config - fdio_config == configs) {
configs += fdio_config
} }
if (crashpad_is_in_chromium) {
deps += [ "//build/config:exe_and_shlib_deps" ]
} else if (crashpad_is_in_fuchsia) {
configs += [ "//build/config/fuchsia:fdio_config" ]
} }
} }
} }
template("crashpad_loadable_module") { template("crashpad_loadable_module") {
loadable_module(target_name) { loadable_module(target_name) {
forward_variables_from(invoker, "*", [ "configs", "remove_configs" ]) forward_variables_from(invoker,
"*",
[
"configs",
"remove_configs",
])
if (defined(invoker.remove_configs)) { if (defined(invoker.remove_configs)) {
configs -= invoker.remove_configs configs -= invoker.remove_configs
} }
@ -95,13 +109,7 @@ template("crashpad_loadable_module") {
configs += invoker.configs configs += invoker.configs
} }
if (!defined(deps)) { if (crashpad_is_in_fuchsia) {
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" ] configs += [ "//build/config/fuchsia:fdio_config" ]
} }
} }

View File

@ -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
}
}

View File

@ -14,8 +14,11 @@
import("crashpad_buildconfig.gni") import("crashpad_buildconfig.gni")
import("test.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) { if (crashpad_is_standalone && crashpad_use_libfuzzer) {
test(target_name) { test(target_name) {
forward_variables_from(invoker, forward_variables_from(invoker,
@ -38,6 +41,15 @@ template("fuzzer_test") {
} }
cflags += [ "-fsanitize=fuzzer" ] 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 { } else {
not_needed(invoker, "*") not_needed(invoker, "*")
group(target_name) { group(target_name) {

View File

@ -21,10 +21,10 @@ import sys
def ChooseDependencyPath(local_path, external_path): 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 The local path, used in standalone builds, is preferred. If it is not
but the external path is, the external path will be used. If neither path is present but the external path is, the external path will be used. If neither
present, the local path will be used, so that error messages uniformly refer path is present, the local path will be used, so that error messages
to the local path. uniformly refer to the local path.
Args: Args:
local_path: The preferred local path to use for a standalone build. local_path: The preferred local path to use for a standalone build.
@ -41,14 +41,14 @@ def ChooseDependencyPath(local_path, external_path):
script_dir = os.path.dirname(__file__) script_dir = os.path.dirname(__file__)
crashpad_dir = (os.path.dirname(script_dir) if script_dir not in ('', os.curdir) crashpad_dir = (os.path.dirname(script_dir)
else os.pardir) if script_dir not in ('', os.curdir) else os.pardir)
sys.path.insert(0, sys.path.insert(
ChooseDependencyPath(os.path.join(crashpad_dir, 'third_party', 'gyp', 'gyp', 0,
'pylib'), ChooseDependencyPath(
os.path.join(crashpad_dir, os.pardir, os.pardir, 'gyp', os.path.join(crashpad_dir, 'third_party', 'gyp', 'gyp', 'pylib'),
'pylib'))[1]) os.path.join(crashpad_dir, os.pardir, os.pardir, 'gyp', 'pylib'))[1])
import gyp import gyp
@ -75,8 +75,8 @@ def main(args):
return result return result
if sys.platform == 'win32': if sys.platform == 'win32':
# Check to make sure that no target_arch was specified. target_arch may be # Check to make sure that no target_arch was specified. target_arch may
# set during a cross build, such as a cross build for Android. # be set during a cross build, such as a cross build for Android.
has_target_arch = False has_target_arch = False
for arg_index in range(0, len(args)): for arg_index in range(0, len(args)):
arg = args[arg_index] arg = args[arg_index]
@ -88,7 +88,8 @@ def main(args):
if not has_target_arch: if not has_target_arch:
# Also generate the x86 build. # Also generate the x86 build.
result = gyp.main(args + ['-D', 'target_arch=ia32', '-G', 'config=Debug']) result = gyp.main(args +
['-D', 'target_arch=ia32', '-G', 'config=Debug'])
if result != 0: if result != 0:
return result return result
result = gyp.main( result = gyp.main(

View File

@ -1,5 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python
# coding: utf-8
# Copyright 2017 The Crashpad Authors. All rights reserved. # Copyright 2017 The Crashpad Authors. All rights reserved.
# #
@ -28,85 +27,42 @@ def main(args):
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description='Set up an Android cross build', description='Set up an Android cross build',
epilog='Additional arguments will be passed to gyp_crashpad.py.') 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') 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) (parsed, extra_command_line_args) = parser.parse_known_args(args)
NDK_ERROR=( ndk_bin_dir = os.path.join(parsed.ndk, 'toolchains', 'llvm', 'prebuilt',
'NDK must be a valid standalone NDK toolchain.\n' + 'linux-x86_64', 'bin')
'See https://developer.android.com/ndk/guides/standalone_toolchain.html') if not os.path.exists(ndk_bin_dir):
arch_dirs = glob.glob(os.path.join(parsed.ndk, '*-linux-android*')) parser.error("missing toolchain")
if len(arch_dirs) != 1:
parser.error(NDK_ERROR)
arch_triplet = os.path.basename(arch_dirs[0]) ARCH_TO_ARCH_TRIPLET = {
ARCH_TRIPLET_TO_ARCH = { 'arm': 'armv7a-linux-androideabi',
'arm-linux-androideabi': 'arm', 'arm64': 'aarch64-linux-android',
'aarch64-linux-android': 'arm64', 'ia32': 'i686-linux-android',
'i686-linux-android': 'ia32', 'x64': 'x86_64-linux-android',
'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]
ndk_bin_dir = os.path.join(parsed.ndk, 'bin') clang_prefix = ARCH_TO_ARCH_TRIPLET[parsed.arch] + parsed.api_level
os.environ['CC_target'] = os.path.join(ndk_bin_dir, clang_prefix + '-clang')
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)
os.environ['CXX_target'] = os.path.join(ndk_bin_dir, 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__ extra_args = ['-D', 'android_api_level=' + parsed.api_level]
# isnt 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 # ARM only includes 'v7a' in the tool prefix for clang
# Clang wrapper wont mention __ANDROID_API__, but the standalone toolchains tool_prefix = ('arm-linux-androideabi' if parsed.arch == 'arm' else
# <android/api-level.h> will #define it for both Clang and GCC. ARCH_TO_ARCH_TRIPLET[parsed.arch])
#
# android_api_level is extracted in this manner even when compiling with Clang
# so that its available for use in GYP conditions that need to test the API
# level, but beware that itll only be available when unified headers are in
# use.
#
# Unified headers are the way of the future, according to
# https://android.googlesource.com/platform/ndk/+/ndk-r14/CHANGELOG.md and
# https://android.googlesource.com/platform/ndk/+/master/docs/UnifiedHeaders.md.
# Traditional (deprecated) headers have been removed entirely as of NDK r16.
# https://android.googlesource.com/platform/ndk/+/ndk-release-r16/CHANGELOG.md.
with open(clang_path, 'r') as file:
clang_script_contents = file.read()
matches = re.finditer(r'\s-D__ANDROID_API__=([\d]+)\s',
clang_script_contents)
match = next(matches, None)
if match:
android_api = int(match.group(1))
extra_args.extend(['-D', 'android_api_level=%d' % android_api])
if next(matches, None):
raise AssertionError('__ANDROID_API__ defined too many times')
for tool in ('ar', 'nm', 'readelf'): for tool in ('ar', 'nm', 'readelf'):
os.environ['%s_target' % tool.upper()] = ( os.environ['%s_target' % tool.upper()] = (os.path.join(
os.path.join(ndk_bin_dir, '%s-%s' % (arch_triplet, tool))) ndk_bin_dir, '%s-%s' % (tool_prefix, tool)))
return gyp_crashpad.main( return gyp_crashpad.main([
['-D', 'OS=android', '-D', 'OS=android', '-D',
'-D', 'target_arch=%s' % arch, 'target_arch=%s' % parsed.arch, '-D', 'clang=1', '-f', 'ninja-android'
'-D', 'clang=%d' % (1 if parsed.compiler == 'clang' else 0), ] + extra_args + extra_command_line_args)
'-f', 'ninja-android'] +
extra_args +
extra_command_line_args)
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -23,7 +23,6 @@ import subprocess
import sys import sys
import urllib2 import urllib2
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
# Sysroot revision from: # Sysroot revision from:
@ -33,11 +32,12 @@ PATH = 'chrome-linux-sysroot/toolchain'
REVISION = '3c248ba4290a5ad07085b7af07e6785bf1ae5b66' REVISION = '3c248ba4290a5ad07085b7af07e6785bf1ae5b66'
FILENAME = 'debian_stretch_amd64_sysroot.tar.xz' FILENAME = 'debian_stretch_amd64_sysroot.tar.xz'
def main(): 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, sysroot = os.path.join(SCRIPT_DIR, os.pardir, 'third_party', 'linux',
'third_party', 'linux', 'sysroot') 'sysroot')
stamp = os.path.join(sysroot, '.stamp') stamp = os.path.join(sysroot, '.stamp')
if os.path.exists(stamp): if os.path.exists(stamp):

BIN
build/ios/Default.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleIdentifier</key>
<string>${IOS_BUNDLE_ID_PREFIX}.gtest.${GTEST_BUNDLE_ID_SUFFIX:rfc1034identifier}</string>
<key>UIApplicationDelegate</key>
<string>CrashpadUnitTestDelegate</string>
</dict>
</plist>

283
build/ios/convert_gn_xcodeproj.py Executable file
View File

@ -0,0 +1,283 @@
#!/usr/bin/env python
# Copyright 2020 The Crashpad Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Convert GN Xcode projects to platform and configuration independent targets.
GN generates Xcode projects that build one configuration only. However, typical
iOS development involves using the Xcode IDE to toggle the platform and
configuration. This script replaces the 'gn' configuration with 'Debug',
'Release' and 'Profile', and changes the ninja invocation to honor these
configurations.
"""
import argparse
import collections
import copy
import filecmp
import json
import hashlib
import os
import plistlib
import random
import shutil
import subprocess
import sys
import tempfile
class XcodeProject(object):
def __init__(self, objects, counter=0):
self.objects = objects
self.counter = 0
def AddObject(self, parent_name, obj):
while True:
self.counter += 1
str_id = "%s %s %d" % (parent_name, obj['isa'], self.counter)
new_id = hashlib.sha1(str_id).hexdigest()[:24].upper()
# Make sure ID is unique. It's possible there could be an id
# conflict since this is run after GN runs.
if new_id not in self.objects:
self.objects[new_id] = obj
return new_id
def CopyFileIfChanged(source_path, target_path):
"""Copy |source_path| to |target_path| is different."""
target_dir = os.path.dirname(target_path)
if not os.path.isdir(target_dir):
os.makedirs(target_dir)
if (not os.path.exists(target_path) or
not filecmp.cmp(source_path, target_path)):
shutil.copyfile(source_path, target_path)
def LoadXcodeProjectAsJSON(path):
"""Return Xcode project at |path| as a JSON string."""
return subprocess.check_output(
['plutil', '-convert', 'json', '-o', '-', path])
def WriteXcodeProject(output_path, json_string):
"""Save Xcode project to |output_path| as XML."""
with tempfile.NamedTemporaryFile() as temp_file:
temp_file.write(json_string)
temp_file.flush()
subprocess.check_call(['plutil', '-convert', 'xml1', temp_file.name])
CopyFileIfChanged(temp_file.name, output_path)
def UpdateProductsProject(file_input, file_output, configurations, root_dir):
"""Update Xcode project to support multiple configurations.
Args:
file_input: path to the input Xcode project
file_output: path to the output file
configurations: list of string corresponding to the configurations that
need to be supported by the tweaked Xcode projects, must contains at
least one value.
"""
json_data = json.loads(LoadXcodeProjectAsJSON(file_input))
project = XcodeProject(json_data['objects'])
objects_to_remove = []
for value in project.objects.values():
isa = value['isa']
# Teach build shell script to look for the configuration and platform.
if isa == 'PBXShellScriptBuildPhase':
value['shellScript'] = value['shellScript'].replace(
'ninja -C .',
'ninja -C "../${CONFIGURATION}${EFFECTIVE_PLATFORM_NAME}"')
# Add new configuration, using the first one as default.
if isa == 'XCConfigurationList':
value['defaultConfigurationName'] = configurations[0]
objects_to_remove.extend(value['buildConfigurations'])
build_config_template = project.objects[value['buildConfigurations']
[0]]
build_settings = build_config_template['buildSettings']
build_settings['CONFIGURATION_BUILD_DIR'] = (
'$(PROJECT_DIR)/../$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)')
build_settings['CODE_SIGN_IDENTITY'] = ''
value['buildConfigurations'] = []
for configuration in configurations:
new_build_config = copy.copy(build_config_template)
new_build_config['name'] = configuration
value['buildConfigurations'].append(
project.AddObject('products', new_build_config))
for object_id in objects_to_remove:
del project.objects[object_id]
AddMarkdownToProject(project, root_dir, json_data['rootObject'])
objects = collections.OrderedDict(sorted(project.objects.iteritems()))
WriteXcodeProject(file_output, json.dumps(json_data))
def AddMarkdownToProject(project, root_dir, root_object):
list_files_cmd = ['git', '-C', root_dir, 'ls-files', '*.md']
paths = subprocess.check_output(list_files_cmd).splitlines()
ios_internal_dir = os.path.join(root_dir, 'ios_internal')
if os.path.exists(ios_internal_dir):
list_files_cmd = ['git', '-C', ios_internal_dir, 'ls-files', '*.md']
ios_paths = subprocess.check_output(list_files_cmd).splitlines()
paths.extend(["ios_internal/" + path for path in ios_paths])
for path in paths:
new_markdown_entry = {
"fileEncoding": "4",
"isa": "PBXFileReference",
"lastKnownFileType": "net.daringfireball.markdown",
"name": os.path.basename(path),
"path": path,
"sourceTree": "<group>"
}
new_markdown_entry_id = project.AddObject('sources', new_markdown_entry)
folder = GetFolderForPath(project, root_object, os.path.dirname(path))
folder['children'].append(new_markdown_entry_id)
def GetFolderForPath(project, rootObject, path):
objects = project.objects
# 'Sources' is always the first child of
# project->rootObject->mainGroup->children.
root = objects[objects[objects[rootObject]['mainGroup']]['children'][0]]
if not path:
return root
for folder in path.split('/'):
children = root['children']
new_root = None
for child in children:
if (objects[child]['isa'] == 'PBXGroup' and
objects[child]['name'] == folder):
new_root = objects[child]
break
if not new_root:
# If the folder isn't found we could just cram it into the leaf
# existing folder, but that leads to folders with tons of README.md
# inside.
new_group = {
"children": [],
"isa": "PBXGroup",
"name": folder,
"sourceTree": "<group>"
}
new_group_id = project.AddObject('sources', new_group)
children.append(new_group_id)
new_root = objects[new_group_id]
root = new_root
return root
def DisableNewBuildSystem(output_dir):
"""Disables the new build system due to crbug.com/852522 """
xcwspacesharedsettings = os.path.join(output_dir, 'all.xcworkspace',
'xcshareddata',
'WorkspaceSettings.xcsettings')
if os.path.isfile(xcwspacesharedsettings):
json_data = json.loads(LoadXcodeProjectAsJSON(xcwspacesharedsettings))
else:
json_data = {}
json_data['BuildSystemType'] = 'Original'
WriteXcodeProject(xcwspacesharedsettings, json.dumps(json_data))
def ConvertGnXcodeProject(root_dir, input_dir, output_dir, configurations):
'''Tweak the Xcode project generated by gn to support multiple
configurations.
The Xcode projects generated by "gn gen --ide" only supports a single
platform and configuration (as the platform and configuration are set per
output directory). This method takes as input such projects and add support
for multiple configurations and platforms (to allow devs to select them in
Xcode).
Args:
input_dir: directory containing the XCode projects created by "gn gen
--ide"
output_dir: directory where the tweaked Xcode projects will be saved
configurations: list of string corresponding to the configurations that
need to be supported by the tweaked Xcode projects, must contains at
least one value.
'''
# Update products project.
products = os.path.join('products.xcodeproj', 'project.pbxproj')
product_input = os.path.join(input_dir, products)
product_output = os.path.join(output_dir, products)
UpdateProductsProject(product_input, product_output, configurations,
root_dir)
# Copy all workspace.
xcwspace = os.path.join('all.xcworkspace', 'contents.xcworkspacedata')
CopyFileIfChanged(os.path.join(input_dir, xcwspace),
os.path.join(output_dir, xcwspace))
# TODO(crbug.com/852522): Disable new BuildSystemType.
DisableNewBuildSystem(output_dir)
# TODO(crbug.com/679110): gn has been modified to remove 'sources.xcodeproj'
# and keep 'all.xcworkspace' and 'products.xcodeproj'. The following code is
# here to support both old and new projects setup and will be removed once
# gn has rolled past it.
sources = os.path.join('sources.xcodeproj', 'project.pbxproj')
if os.path.isfile(os.path.join(input_dir, sources)):
CopyFileIfChanged(os.path.join(input_dir, sources),
os.path.join(output_dir, sources))
def Main(args):
parser = argparse.ArgumentParser(
description='Convert GN Xcode projects for iOS.')
parser.add_argument(
'input', help='directory containing [product|all] Xcode projects.')
parser.add_argument(
'output', help='directory where to generate the iOS configuration.')
parser.add_argument('--add-config',
dest='configurations',
default=[],
action='append',
help='configuration to add to the Xcode project')
parser.add_argument('--root',
type=os.path.abspath,
required=True,
help='root directory of the project')
args = parser.parse_args(args)
if not os.path.isdir(args.input):
sys.stderr.write('Input directory does not exists.\n')
return 1
required = set(['products.xcodeproj', 'all.xcworkspace'])
if not required.issubset(os.listdir(args.input)):
sys.stderr.write(
'Input directory does not contain all necessary Xcode projects.\n')
return 1
if not args.configurations:
sys.stderr.write(
'At least one configuration required, see --add-config.\n')
return 1
ConvertGnXcodeProject(args.root, args.input, args.output,
args.configurations)
if __name__ == '__main__':
sys.exit(Main(sys.argv[1:]))

View File

@ -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.

350
build/ios/setup_ios_gn.py Executable file
View File

@ -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:]))

View File

@ -13,7 +13,6 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
"""Helper script to [re]start or stop a helper Fuchsia QEMU instance to be used """Helper script to [re]start or stop a helper Fuchsia QEMU instance to be used
for running tests without a device. for running tests without a device.
""" """
@ -53,12 +52,15 @@ def _CheckForTun():
"""Check for networking. TODO(scottmg): Currently, this is Linux-specific. """Check for networking. TODO(scottmg): Currently, this is Linux-specific.
""" """
returncode = subprocess.call( returncode = subprocess.call(
['tunctl', '-b', '-u', getpass.getuser(), '-t', 'qemu'], ['tunctl', '-b', '-u',
stdout=DEVNULL, stderr=DEVNULL) getpass.getuser(), '-t', 'qemu'],
stdout=DEVNULL,
stderr=DEVNULL)
if returncode != 0: if returncode != 0:
print('To use QEMU with networking on Linux, configure TUN/TAP. See:', print('To use QEMU with networking on Linux, configure TUN/TAP. See:',
file=sys.stderr) file=sys.stderr)
print(' https://fuchsia.googlesource.com/zircon/+/HEAD/docs/qemu.md#enabling-networking-under-qemu-x86_64-only', print(
' https://fuchsia.googlesource.com/zircon/+/HEAD/docs/qemu.md#enabling-networking-under-qemu-x86_64-only',
file=sys.stderr) file=sys.stderr)
return 2 return 2
return 0 return 0
@ -78,10 +80,13 @@ def _Start(pid_file):
initrd_path = os.path.join(kernel_data_dir, 'bootdata.bin') initrd_path = os.path.join(kernel_data_dir, 'bootdata.bin')
mac_tail = ':'.join('%02x' % random.randint(0, 255) for x in range(3)) mac_tail = ':'.join('%02x' % random.randint(0, 255) for x in range(3))
instance_name = 'crashpad_qemu_' + \ instance_name = (
''.join(chr(random.randint(ord('A'), ord('Z'))) for x in range(8)) '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. # These arguments are from the Fuchsia repo in zircon/scripts/run-zircon.
# yapf: disable
popen = subprocess.Popen([ popen = subprocess.Popen([
qemu_path, qemu_path,
'-m', '2048', '-m', '2048',
@ -97,15 +102,21 @@ def _Start(pid_file):
'-netdev', 'type=tap,ifname=qemu,script=no,downscript=no,id=net0', '-netdev', 'type=tap,ifname=qemu,script=no,downscript=no,id=net0',
'-device', 'e1000,netdev=net0,mac=52:54:00:' + mac_tail, '-device', 'e1000,netdev=net0,mac=52:54:00:' + mac_tail,
'-append', 'TERM=dumb zircon.nodename=' + instance_name, '-append', 'TERM=dumb zircon.nodename=' + instance_name,
], stdin=DEVNULL, stdout=DEVNULL, stderr=DEVNULL) ],
stdin=DEVNULL,
stdout=DEVNULL,
stderr=DEVNULL)
# yapf: enable
with open(pid_file, 'wb') as f: with open(pid_file, 'wb') as f:
f.write('%d\n' % popen.pid) f.write('%d\n' % popen.pid)
for i in range(10): for i in range(10):
netaddr_path = os.path.join(fuchsia_dir, 'sdk', arch, 'tools', 'netaddr') netaddr_path = os.path.join(fuchsia_dir, 'sdk', arch, 'tools',
'netaddr')
if subprocess.call([netaddr_path, '--nowait', instance_name], if subprocess.call([netaddr_path, '--nowait', instance_name],
stdout=open(os.devnull), stderr=open(os.devnull)) == 0: stdout=open(os.devnull),
stderr=open(os.devnull)) == 0:
break break
time.sleep(.5) time.sleep(.5)
else: else:

View File

@ -24,6 +24,7 @@ import posixpath
import re import re
import subprocess import subprocess
import sys import sys
import tempfile
import uuid import uuid
CRASHPAD_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), CRASHPAD_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)),
@ -33,8 +34,8 @@ IS_WINDOWS_HOST = sys.platform.startswith('win')
def _FindGNFromBinaryDir(binary_dir): def _FindGNFromBinaryDir(binary_dir):
"""Attempts to determine the path to a GN binary used to generate the build """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 files in the given binary_dir. This is necessary because `gn` might not be
the path or might be in a non-standard location, particularly on build in the path or might be in a non-standard location, particularly on build
machines.""" machines."""
build_ninja = os.path.join(binary_dir, 'build.ninja') build_ninja = os.path.join(binary_dir, 'build.ninja')
@ -57,24 +58,27 @@ def _FindGNFromBinaryDir(binary_dir):
if line.startswith(' command = '): if line.startswith(' command = '):
gn_command_line_parts = line.strip().split(' ') gn_command_line_parts = line.strip().split(' ')
if len(gn_command_line_parts) > 2: if len(gn_command_line_parts) > 2:
return os.path.join(binary_dir, gn_command_line_parts[2]) return os.path.join(binary_dir,
gn_command_line_parts[2])
return None return None
def _BinaryDirTargetOS(binary_dir): def _BinaryDirTargetOS(binary_dir):
"""Returns the apparent target OS of binary_dir, or None if none appear to be """Returns the apparent target OS of binary_dir, or None if none appear to
explicitly specified.""" be explicitly specified."""
gn_path = _FindGNFromBinaryDir(binary_dir) gn_path = _FindGNFromBinaryDir(binary_dir)
if gn_path: if gn_path:
# Look for a GN “target_os”. # Look for a GN “target_os”.
popen = subprocess.Popen([gn_path, '--root=' + CRASHPAD_DIR, popen = subprocess.Popen([
'args', binary_dir, gn_path, '--root=' + CRASHPAD_DIR, 'args', binary_dir,
'--list=target_os', '--short'], '--list=target_os', '--short'
],
shell=IS_WINDOWS_HOST, shell=IS_WINDOWS_HOST,
stdout=subprocess.PIPE, stderr=open(os.devnull)) stdout=subprocess.PIPE,
stderr=open(os.devnull))
value = popen.communicate()[0] value = popen.communicate()[0]
if popen.returncode == 0: if popen.returncode == 0:
match = re.match('target_os = "(.*)"$', value.decode('utf-8')) match = re.match('target_os = "(.*)"$', value.decode('utf-8'))
@ -87,8 +91,7 @@ def _BinaryDirTargetOS(binary_dir):
if os.path.exists(build_ninja_path): if os.path.exists(build_ninja_path):
with open(build_ninja_path) as build_ninja_file: with open(build_ninja_path) as build_ninja_file:
build_ninja_content = build_ninja_file.read() build_ninja_content = build_ninja_file.read()
match = re.search('^ar = .+-linux-android(eabi)?-ar$', match = re.search('-linux-android(eabi)?-ar$', build_ninja_content,
build_ninja_content,
re.MULTILINE) re.MULTILINE)
if match: if match:
return 'android' return 'android'
@ -118,8 +121,8 @@ def _EnableVTProcessingOnWindowsConsole():
try: try:
# From <wincon.h>. This would be # From <wincon.h>. This would be
# win32console.ENABLE_VIRTUAL_TERMINAL_PROCESSING, but its too new to be # win32console.ENABLE_VIRTUAL_TERMINAL_PROCESSING, but its too new to
# defined there. # be defined there.
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
stdout_console.SetConsoleMode(console_mode | stdout_console.SetConsoleMode(console_mode |
@ -147,8 +150,8 @@ def _RunOnAndroidTarget(binary_dir, test, android_device, extra_command_line):
return return
def _adb(*args): def _adb(*args):
# Flush all of this scripts own buffered stdout output before running adb, # Flush all of this scripts own buffered stdout output before running
# which will likely produce its own output on stdout. # adb, which will likely produce its own output on stdout.
sys.stdout.flush() sys.stdout.flush()
adb_command = ['adb', '-s', android_device] adb_command = ['adb', '-s', android_device]
@ -161,33 +164,34 @@ def _RunOnAndroidTarget(binary_dir, test, android_device, extra_command_line):
_adb('push', *args) _adb('push', *args)
def _adb_shell(command_args, env={}): def _adb_shell(command_args, env={}):
# Build a command to execute via “sh -c” instead of invoking it directly. # Build a command to execute via “sh -c” instead of invoking it
# Heres why: # directly. Heres why:
# #
# /system/bin/env isnt normally present prior to Android 6.0 (M), where # /system/bin/env isnt normally present prior to Android 6.0 (M), where
# toybox was introduced (Android platform/manifest 9a2c01e8450b). Instead, # toybox was introduced (Android platform/manifest 9a2c01e8450b).
# set environment variables by using the shells internal “export” command. # Instead, set environment variables by using the shells internal
# “export” command.
# #
# adbd prior to Android 7.0 (N), and the adb client prior to SDK # adbd prior to Android 7.0 (N), and the adb client prior to SDK
# platform-tools version 24, dont know how to communicate a shell commands # platform-tools version 24, dont know how to communicate a shell
# exit status. This was added in Android platform/system/core 606835ae5c4b). # commands exit status. This was added in Android platform/system/core
# With older adb servers and clients, adb will “exit 0” indicating success # 606835ae5c4b). With older adb servers and clients, adb will “exit 0”
# even if the command failed on the device. This makes # indicating success even if the command failed on the device. This
# subprocess.check_call() semantics difficult to implement directly. As a # makes subprocess.check_call() semantics difficult to implement
# workaround, have the device send the commands exit status over stdout and # directly. As a workaround, have the device send the commands exit
# pick it back up in this function. # status over stdout and pick it back up in this function.
# #
# Both workarounds are implemented by giving the device a simple script, # Both workarounds are implemented by giving the device a simple script,
# which adbd will run as an “sh -c” argument. # which adbd will run as an “sh -c” argument.
adb_command = ['adb', '-s', android_device, 'shell'] adb_command = ['adb', '-s', android_device, 'shell']
script_commands = [] script_commands = []
for k, v in env.items(): for k, v in env.items():
script_commands.append('export %s=%s' % (pipes.quote(k), pipes.quote(v))) script_commands.append('export %s=%s' %
(pipes.quote(k), pipes.quote(v)))
script_commands.extend([ script_commands.extend([
' '.join(pipes.quote(x) for x in command_args), ' '.join(pipes.quote(x) for x in command_args), 'status=${?}',
'status=${?}', 'echo "status=${status}"', 'exit ${status}'
'echo "status=${status}"', ])
'exit ${status}'])
adb_command.append('; '.join(script_commands)) adb_command.append('; '.join(script_commands))
child = subprocess.Popen(adb_command, child = subprocess.Popen(adb_command,
shell=IS_WINDOWS_HOST, shell=IS_WINDOWS_HOST,
@ -211,9 +215,9 @@ def _RunOnAndroidTarget(binary_dir, test, android_device, extra_command_line):
print(data, end='') print(data, end='')
if final_line is None: if final_line is None:
# Maybe there was some stderr output after the end of stdout. Old versions # Maybe there was some stderr output after the end of stdout. Old
# of adb, prior to when the exit status could be communicated, smush the # versions of adb, prior to when the exit status could be
# two together. # communicated, smush the two together.
raise subprocess.CalledProcessError(-1, adb_command) raise subprocess.CalledProcessError(-1, adb_command)
status = int(FINAL_LINE_RE.match(final_line.rstrip()).group(1)) status = int(FINAL_LINE_RE.match(final_line.rstrip()).group(1))
if status != 0: if status != 0:
@ -221,23 +225,24 @@ def _RunOnAndroidTarget(binary_dir, test, android_device, extra_command_line):
child.wait() child.wait()
if child.returncode != 0: if child.returncode != 0:
raise subprocess.CalledProcessError(subprocess.returncode, adb_command) raise subprocess.CalledProcessError(subprocess.returncode,
adb_command)
# /system/bin/mktemp isnt normally present prior to Android 6.0 (M), where # /system/bin/mktemp isnt normally present prior to Android 6.0 (M), where
# toybox was introduced (Android platform/manifest 9a2c01e8450b). Fake it with # toybox was introduced (Android platform/manifest 9a2c01e8450b). Fake it
# a host-generated name. This wont retry if the name is in use, but with 122 # with a host-generated name. This wont retry if the name is in use, but
# bits of randomness, it should be OK. This uses “mkdir” instead of “mkdir -p” # with 122 bits of randomness, it should be OK. This uses “mkdir” instead of
# because the latter will not indicate failure if the directory already # “mkdir -p”because the latter will not indicate failure if the directory
# exists. # already exists.
device_temp_dir = '/data/local/tmp/%s.%s' % (test, uuid.uuid4().hex) device_temp_dir = '/data/local/tmp/%s.%s' % (test, uuid.uuid4().hex)
_adb_shell(['mkdir', device_temp_dir]) _adb_shell(['mkdir', device_temp_dir])
try: try:
# Specify test dependencies that must be pushed to the device. This could be # Specify test dependencies that must be pushed to the device. This
# determined automatically in a GN build, following the example used for # could be determined automatically in a GN build, following the example
# Fuchsia. Since nothing like that exists for GYP, hard-code it for # used for Fuchsia. Since nothing like that exists for GYP, hard-code it
# supported tests. # for supported tests.
test_build_artifacts = [test] test_build_artifacts = [test, 'crashpad_handler']
test_data = ['test/test_paths_test_data_root.txt'] test_data = ['test/test_paths_test_data_root.txt']
if test == 'crashpad_test_test': if test == 'crashpad_test_test':
@ -250,12 +255,13 @@ def _RunOnAndroidTarget(binary_dir, test, android_device, extra_command_line):
device_out_dir = posixpath.join(device_temp_dir, 'out') device_out_dir = posixpath.join(device_temp_dir, 'out')
device_mkdirs = [device_out_dir] device_mkdirs = [device_out_dir]
for source_path in test_data: for source_path in test_data:
# A trailing slash could reasonably mean to copy an entire directory, but # A trailing slash could reasonably mean to copy an entire
# will interfere with whats needed from the path split. All parent # directory, but will interfere with whats needed from the path
# directories of any source_path need to be be represented in # split. All parent directories of any source_path need to be be
# device_mkdirs, but its important that no source_path itself wind up in # represented in device_mkdirs, but its important that no
# device_mkdirs, even if source_path names a directory, because that would # source_path itself wind up in device_mkdirs, even if source_path
# cause the “adb push” of the directory below to behave incorrectly. # names a directory, because that would cause the “adb push” of the
# directory below to behave incorrectly.
if source_path.endswith(posixpath.sep): if source_path.endswith(posixpath.sep):
source_path = source_path[:-1] source_path = source_path[:-1]
@ -270,7 +276,8 @@ def _RunOnAndroidTarget(binary_dir, test, android_device, extra_command_line):
# Push the test binary and any other build output to the device. # Push the test binary and any other build output to the device.
local_test_build_artifacts = [] local_test_build_artifacts = []
for artifact in test_build_artifacts: for artifact in test_build_artifacts:
local_test_build_artifacts.append(os.path.join(binary_dir, artifact)) local_test_build_artifacts.append(os.path.join(
binary_dir, artifact))
_adb_push(local_test_build_artifacts, device_out_dir) _adb_push(local_test_build_artifacts, device_out_dir)
# Push test data to the device. # Push test data to the device.
@ -278,20 +285,21 @@ def _RunOnAndroidTarget(binary_dir, test, android_device, extra_command_line):
_adb_push([os.path.join(CRASHPAD_DIR, source_path)], _adb_push([os.path.join(CRASHPAD_DIR, source_path)],
posixpath.join(device_temp_dir, source_path)) posixpath.join(device_temp_dir, source_path))
# Run the test on the device. Pass the test data root in the environment. # 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 # Because the test will not run with its standard output attached to a
# pseudo-terminal device, gtest will not normally enable colored output, so # pseudo-terminal device, gtest will not normally enable colored output,
# mimic gtests own logic for deciding whether to enable color by checking # so mimic gtests own logic for deciding whether to enable color by
# this scripts own standard output connection. The whitelist of TERM values # checking this scripts own standard output connection. The whitelist
# comes from gtest googletest/src/gtest.cc # of TERM values comes from gtest googletest/src/gtest.cc
# testing::internal::ShouldUseColor(). # testing::internal::ShouldUseColor().
env = {'CRASHPAD_TEST_DATA_ROOT': device_temp_dir} env = {'CRASHPAD_TEST_DATA_ROOT': device_temp_dir}
gtest_color = os.environ.get('GTEST_COLOR') gtest_color = os.environ.get('GTEST_COLOR')
if gtest_color in ('auto', None): if gtest_color in ('auto', None):
if (sys.stdout.isatty() and if (sys.stdout.isatty() and
(os.environ.get('TERM') in (os.environ.get('TERM')
('xterm', 'xterm-color', 'xterm-256color', 'screen', in ('xterm', 'xterm-color', 'xterm-256color', 'screen',
'screen-256color', 'tmux', 'tmux-256color', 'rxvt-unicode', 'screen-256color', 'tmux', 'tmux-256color', 'rxvt-unicode',
'rxvt-unicode-256color', 'linux', 'cygwin') or 'rxvt-unicode-256color', 'linux', 'cygwin') or
(IS_WINDOWS_HOST and _EnableVTProcessingOnWindowsConsole()))): (IS_WINDOWS_HOST and _EnableVTProcessingOnWindowsConsole()))):
@ -299,7 +307,8 @@ def _RunOnAndroidTarget(binary_dir, test, android_device, extra_command_line):
else: else:
gtest_color = 'no' gtest_color = 'no'
env['GTEST_COLOR'] = gtest_color env['GTEST_COLOR'] = gtest_color
_adb_shell([posixpath.join(device_out_dir, test)] + extra_command_line, env) _adb_shell([posixpath.join(device_out_dir, test)] + extra_command_line,
env)
finally: finally:
_adb_shell(['rm', '-rf', device_temp_dir]) _adb_shell(['rm', '-rf', device_temp_dir])
@ -315,9 +324,10 @@ def _GenerateFuchsiaRuntimeDepsFiles(binary_dir, tests):
with open(targets_file, 'wb') as f: with open(targets_file, 'wb') as f:
f.write('//:' + '\n//:'.join(tests) + '\n') f.write('//:' + '\n//:'.join(tests) + '\n')
gn_path = _FindGNFromBinaryDir(binary_dir) gn_path = _FindGNFromBinaryDir(binary_dir)
subprocess.check_call( subprocess.check_call([
[gn_path, '--root=' + CRASHPAD_DIR, 'gen', binary_dir, gn_path, '--root=' + CRASHPAD_DIR, 'gen', binary_dir,
'--runtime-deps-list-file=' + targets_file]) '--runtime-deps-list-file=' + targets_file
])
# Run again so that --runtime-deps-list-file isn't in the regen rule. See # Run again so that --runtime-deps-list-file isn't in the regen rule. See
# https://crbug.com/814816. # https://crbug.com/814816.
@ -357,7 +367,9 @@ def _RunOnFuchsiaTarget(binary_dir, test, device_name, extra_command_line):
# Run loglistener and filter the output to know when the test is done. # Run loglistener and filter the output to know when the test is done.
loglistener_process = subprocess.Popen( loglistener_process = subprocess.Popen(
[os.path.join(sdk_root, 'tools', 'loglistener'), device_name], [os.path.join(sdk_root, 'tools', 'loglistener'), device_name],
stdout=subprocess.PIPE, stdin=open(os.devnull), stderr=open(os.devnull)) stdout=subprocess.PIPE,
stdin=open(os.devnull),
stderr=open(os.devnull))
runtime_deps_file = os.path.join(binary_dir, test + '.runtime_deps') runtime_deps_file = os.path.join(binary_dir, test + '.runtime_deps')
with open(runtime_deps_file, 'rb') as f: with open(runtime_deps_file, 'rb') as f:
@ -368,8 +380,8 @@ def _RunOnFuchsiaTarget(binary_dir, test, device_name, extra_command_line):
by using pipes.quote(), and then each command is chained by shell ';'. by using pipes.quote(), and then each command is chained by shell ';'.
""" """
netruncmd_path = os.path.join(sdk_root, 'tools', 'netruncmd') netruncmd_path = os.path.join(sdk_root, 'tools', 'netruncmd')
final_args = ' ; '.join(' '.join(pipes.quote(x) for x in command) final_args = ' ; '.join(
for command in args) ' '.join(pipes.quote(x) for x in command) for command in args)
subprocess.check_call([netruncmd_path, device_name, final_args]) subprocess.check_call([netruncmd_path, device_name, final_args])
try: try:
@ -379,35 +391,38 @@ def _RunOnFuchsiaTarget(binary_dir, test, device_name, extra_command_line):
staging_root = test_root + '/pkg' staging_root = test_root + '/pkg'
# Make a staging directory tree on the target. # Make a staging directory tree on the target.
directories_to_create = [tmp_root, directories_to_create = [
tmp_root,
'%s/bin' % staging_root, '%s/bin' % staging_root,
'%s/assets' % staging_root] '%s/assets' % staging_root
]
netruncmd(['mkdir', '-p'] + directories_to_create) netruncmd(['mkdir', '-p'] + directories_to_create)
def netcp(local_path): def netcp(local_path):
"""Uses `netcp` to copy a file or directory to the device. Files located """Uses `netcp` to copy a file or directory to the device. Files
inside the build dir are stored to /pkg/bin, otherwise to /pkg/assets. located inside the build dir are stored to /pkg/bin, otherwise to
.so files are stored somewhere completely different, into /boot/lib (!). /pkg/assets. .so files are stored somewhere completely different,
This is because the loader service does not yet correctly handle the into /boot/lib (!). This is because the loader service does not yet
namespace in which the caller is being run, and so can only load .so files correctly handle the namespace in which the caller is being run, and
from a couple hardcoded locations, the only writable one of which is so can only load .so files from a couple hardcoded locations, the
/boot/lib, so we copy all .so files there. This bug is filed upstream as only writable one of which is /boot/lib, so we copy all .so files
ZX-1619. there. This bug is filed upstream as ZX-1619.
""" """
in_binary_dir = local_path.startswith(binary_dir + '/') in_binary_dir = local_path.startswith(binary_dir + '/')
if in_binary_dir: if in_binary_dir:
if local_path.endswith('.so'): if local_path.endswith('.so'):
target_path = os.path.join( target_path = os.path.join('/boot/lib',
'/boot/lib', local_path[len(binary_dir)+1:]) local_path[len(binary_dir) + 1:])
else: else:
target_path = os.path.join( target_path = os.path.join(staging_root, 'bin',
staging_root, 'bin', local_path[len(binary_dir)+1:]) local_path[len(binary_dir) + 1:])
else: else:
relative_path = os.path.relpath(local_path, CRASHPAD_DIR) relative_path = os.path.relpath(local_path, CRASHPAD_DIR)
target_path = os.path.join(staging_root, 'assets', relative_path) target_path = os.path.join(staging_root, 'assets',
relative_path)
netcp_path = os.path.join(sdk_root, 'tools', 'netcp') netcp_path = os.path.join(sdk_root, 'tools', 'netcp')
subprocess.check_call([netcp_path, local_path, subprocess.check_call(
device_name + ':' + target_path], [netcp_path, local_path, device_name + ':' + target_path],
stderr=open(os.devnull)) stderr=open(os.devnull))
# Copy runtime deps into the staging tree. # Copy runtime deps into the staging tree.
@ -422,19 +437,87 @@ def _RunOnFuchsiaTarget(binary_dir, test, device_name, extra_command_line):
done_message = 'TERMINATED: ' + unique_id done_message = 'TERMINATED: ' + unique_id
namespace_command = [ namespace_command = [
'namespace', '/pkg=' + staging_root, '/tmp=' + tmp_root, '/svc=/svc', 'namespace', '/pkg=' + staging_root, '/tmp=' + tmp_root,
'--replace-child-argv0=/pkg/bin/' + test, '--', '/svc=/svc', '--replace-child-argv0=/pkg/bin/' + test, '--',
staging_root + '/bin/' + test] + extra_command_line staging_root + '/bin/' + test
] + extra_command_line
netruncmd(namespace_command, ['echo', done_message]) netruncmd(namespace_command, ['echo', done_message])
success = _HandleOutputFromFuchsiaLogListener( success = _HandleOutputFromFuchsiaLogListener(loglistener_process,
loglistener_process, done_message) done_message)
if not success: if not success:
raise subprocess.CalledProcessError(1, test) raise subprocess.CalledProcessError(1, test)
finally: finally:
netruncmd(['rm', '-rf', test_root]) 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:
plistlib.writePlist(xctest(binary_dir, test), xctestrun_path)
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 # 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 # that are run is maintained in-tree, rather than in a separate infrastructure
# location in the recipe. # location in the recipe.
@ -447,11 +530,11 @@ def main(args):
args = parser.parse_args() args = parser.parse_args()
# Tell 64-bit Windows tests where to find 32-bit test executables, for # 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 # cross-bitted testing. This relies on the fact that the GYP build by
# uses {Debug,Release} for the 32-bit build and {Debug,Release}_x64 for the # default uses {Debug,Release} for the 32-bit build and {Debug,Release}_x64
# 64-bit build. This is not a universally valid assumption, and if its not # for the 64-bit build. This is not a universally valid assumption, and if
# met, 64-bit tests that require 32-bit build output will disable themselves # its not met, 64-bit tests that require 32-bit build output will disable
# dynamically. # themselves dynamically.
if (sys.platform == 'win32' and args.binary_dir.endswith('_x64') and if (sys.platform == 'win32' and args.binary_dir.endswith('_x64') and
'CRASHPAD_TEST_32_BIT_OUTPUT' not in os.environ): 'CRASHPAD_TEST_32_BIT_OUTPUT' not in os.environ):
binary_dir_32 = args.binary_dir[:-4] binary_dir_32 = args.binary_dir[:-4]
@ -461,6 +544,7 @@ def main(args):
target_os = _BinaryDirTargetOS(args.binary_dir) target_os = _BinaryDirTargetOS(args.binary_dir)
is_android = target_os == 'android' is_android = target_os == 'android'
is_fuchsia = target_os == 'fuchsia' is_fuchsia = target_os == 'fuchsia'
is_ios = target_os == 'ios'
tests = [ tests = [
'crashpad_client_test', 'crashpad_client_test',
@ -480,13 +564,13 @@ def main(args):
for line in adb_devices.splitlines(): for line in adb_devices.splitlines():
line = line.decode('utf-8') line = line.decode('utf-8')
if (line == 'List of devices attached' or if (line == 'List of devices attached' or
re.match('^\* daemon .+ \*$', line) or re.match('^\* daemon .+ \*$', line) or line == ''):
line == ''):
continue continue
(device, ignore) = line.split('\t') (device, ignore) = line.split('\t')
devices.append(device) devices.append(device)
if len(devices) != 1: if len(devices) != 1:
print("Please set ANDROID_DEVICE to your device's id", file=sys.stderr) print("Please set ANDROID_DEVICE to your device's id",
file=sys.stderr)
return 2 return 2
android_device = devices[0] android_device = devices[0]
print('Using autodetected Android device:', android_device) print('Using autodetected Android device:', android_device)
@ -494,7 +578,8 @@ def main(args):
zircon_nodename = os.environ.get('ZIRCON_NODENAME') zircon_nodename = os.environ.get('ZIRCON_NODENAME')
if not zircon_nodename: if not zircon_nodename:
netls = os.path.join(_GetFuchsiaSDKRoot(), 'tools', 'netls') netls = os.path.join(_GetFuchsiaSDKRoot(), 'tools', 'netls')
popen = subprocess.Popen([netls, '--nowait'], stdout=subprocess.PIPE) popen = subprocess.Popen([netls, '--nowait'],
stdout=subprocess.PIPE)
devices = popen.communicate()[0].splitlines() devices = popen.communicate()[0].splitlines()
if popen.returncode != 0 or len(devices) != 1: if popen.returncode != 0 or len(devices) != 1:
print("Please set ZIRCON_NODENAME to your device's hostname", print("Please set ZIRCON_NODENAME to your device's hostname",
@ -504,6 +589,8 @@ def main(args):
print('Using autodetected Fuchsia device:', zircon_nodename) print('Using autodetected Fuchsia device:', zircon_nodename)
_GenerateFuchsiaRuntimeDepsFiles( _GenerateFuchsiaRuntimeDepsFiles(
args.binary_dir, [t for t in tests if not t.endswith('.py')]) 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: elif IS_WINDOWS_HOST:
tests.append('snapshot/win/end_to_end_test.py') tests.append('snapshot/win/end_to_end_test.py')
@ -519,8 +606,10 @@ def main(args):
print(test) print(test)
print('-' * 80) print('-' * 80)
if test.endswith('.py'): if test.endswith('.py'):
subprocess.check_call( subprocess.check_call([
[sys.executable, os.path.join(CRASHPAD_DIR, test), args.binary_dir]) sys.executable,
os.path.join(CRASHPAD_DIR, test), args.binary_dir
])
else: else:
extra_command_line = [] extra_command_line = []
if args.gtest_filter: if args.gtest_filter:
@ -531,6 +620,10 @@ def main(args):
elif is_fuchsia: elif is_fuchsia:
_RunOnFuchsiaTarget(args.binary_dir, test, zircon_nodename, _RunOnFuchsiaTarget(args.binary_dir, test, zircon_nodename,
extra_command_line) extra_command_line)
elif is_ios:
_RunOnIOSTarget(args.binary_dir,
test,
is_xcuitest=test.startswith('ios'))
else: else:
subprocess.check_call([os.path.join(args.binary_dir, test)] + subprocess.check_call([os.path.join(args.binary_dir, test)] +
extra_command_line) extra_command_line)

View File

@ -18,9 +18,32 @@ if (crashpad_is_in_chromium) {
import("//testing/test.gni") import("//testing/test.gni")
} else { } else {
template("test") { template("test") {
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) { executable(target_name) {
testonly = true testonly = true
forward_variables_from(invoker, "*") forward_variables_from(invoker, "*")
} }
} }
}
} }

View File

@ -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) { if (crashpad_is_linux || crashpad_is_android) {
set_sources_assignment_filter([]) set_sources_assignment_filter([])
sources += [ sources += [
@ -77,22 +84,36 @@ static_library("client") {
public_configs = [ "..:crashpad_config" ] public_configs = [ "..:crashpad_config" ]
deps = [ public_deps = [
"../compat",
"../third_party/mini_chromium:base", "../third_party/mini_chromium:base",
"../util", "../util",
] ]
deps = []
if (crashpad_is_win) { if (crashpad_is_win) {
libs = [ "rpcrt4.lib" ] libs = [ "rpcrt4.lib" ]
cflags = [ "/wd4201" ] # nonstandard extension used : nameless struct/union 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 += [ 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") { source_set("client_test") {
@ -116,6 +137,17 @@ source_set("client_test") {
sources += [ "crashpad_client_win_test.cc" ] 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) { if (crashpad_is_linux || crashpad_is_android) {
sources += [ "crashpad_client_linux_test.cc" ] sources += [ "crashpad_client_linux_test.cc" ]
} }
@ -131,9 +163,9 @@ source_set("client_test") {
"../util", "../util",
] ]
data_deps = [ if (!crashpad_is_ios && !crashpad_is_fuchsia) {
"../handler:crashpad_handler", data_deps = [ "../handler:crashpad_handler" ]
] }
if (crashpad_is_win) { if (crashpad_is_win) {
data_deps += [ "../handler:crashpad_handler_console" ] data_deps += [ "../handler:crashpad_handler_console" ]

View File

@ -23,6 +23,7 @@
'dependencies': [ 'dependencies': [
'../compat/compat.gyp:crashpad_compat', '../compat/compat.gyp:crashpad_compat',
'../third_party/mini_chromium/mini_chromium.gyp:base', '../third_party/mini_chromium/mini_chromium.gyp:base',
'../third_party/lss/lss.gyp:lss',
'../util/util.gyp:crashpad_util', '../util/util.gyp:crashpad_util',
], ],
'include_dirs': [ 'include_dirs': [

View File

@ -61,14 +61,14 @@ std::vector<std::string> BuildHandlerArgvStrings(
return argv_strings; return argv_strings;
} }
void ConvertArgvStrings(const std::vector<std::string>& argv_strings, void StringVectorToCStringVector(const std::vector<std::string>& strings,
std::vector<const char*>* argv) { std::vector<const char*>* c_strings) {
argv->clear(); c_strings->clear();
argv->reserve(argv_strings.size() + 1); c_strings->reserve(strings.size() + 1);
for (const auto& arg : argv_strings) { for (const auto& str : strings) {
argv->push_back(arg.c_str()); c_strings->push_back(str.c_str());
} }
argv->push_back(nullptr); c_strings->push_back(nullptr);
} }
} // namespace crashpad } // namespace crashpad

View File

@ -40,11 +40,11 @@ std::vector<std::string> BuildHandlerArgvStrings(
//! \brief Flattens a string vector into a const char* vector suitable for use //! \brief Flattens a string vector into a const char* vector suitable for use
//! in an exec() call. //! in an exec() call.
//! //!
//! \param[in] argv_strings Arguments to be passed to child process, typically //! \param[in] strings A vector of string data. This vector must remain valid
//! created by BuildHandlerArgvStrings(). //! for the lifetime of \a c_strings.
//! \param[out] argv argv suitable for starting the child process. //! \param[out] c_strings A vector of pointers to the string data in \a strings.
void ConvertArgvStrings(const std::vector<std::string>& argv_strings, void StringVectorToCStringVector(const std::vector<std::string>& strings,
std::vector<const char*>* argv); std::vector<const char*>* c_strings);
} // namespace crashpad } // namespace crashpad

View File

@ -27,7 +27,8 @@ CrashReportDatabase::Report::Report()
uploaded(false), uploaded(false),
last_upload_attempt_time(0), last_upload_attempt_time(0),
upload_attempts(0), upload_attempts(0),
upload_explicitly_requested(false) {} upload_explicitly_requested(false),
total_size(0u) {}
CrashReportDatabase::NewReport::NewReport() CrashReportDatabase::NewReport::NewReport()
: writer_(std::make_unique<FileWriter>()), : writer_(std::make_unique<FileWriter>()),
@ -64,6 +65,15 @@ bool CrashReportDatabase::NewReport::Initialize(
return true; return true;
} }
FileReaderInterface* CrashReportDatabase::NewReport::Reader() {
auto reader = std::make_unique<FileReader>();
if (!reader->Open(file_remover_.get())) {
return nullptr;
}
reader_ = std::move(reader);
return reader_.get();
}
CrashReportDatabase::UploadReport::UploadReport() CrashReportDatabase::UploadReport::UploadReport()
: Report(), : Report(),
reader_(std::make_unique<FileReader>()), reader_(std::make_unique<FileReader>()),

View File

@ -15,6 +15,7 @@
#ifndef CRASHPAD_CLIENT_CRASH_REPORT_DATABASE_H_ #ifndef CRASHPAD_CLIENT_CRASH_REPORT_DATABASE_H_
#define CRASHPAD_CLIENT_CRASH_REPORT_DATABASE_H_ #define CRASHPAD_CLIENT_CRASH_REPORT_DATABASE_H_
#include <stdint.h>
#include <time.h> #include <time.h>
#include <map> #include <map>
@ -98,6 +99,10 @@ class CrashReportDatabase {
//! Whether this crash report was explicitly requested by user to be //! Whether this crash report was explicitly requested by user to be
//! uploaded. This can be true only if report is in the 'pending' state. //! uploaded. This can be true only if report is in the 'pending' state.
bool upload_explicitly_requested; 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. //! \brief A crash report that is in the process of being written.
@ -108,9 +113,13 @@ class CrashReportDatabase {
NewReport(); NewReport();
~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(); } 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 //! A unique identifier by which this report will always be known to the
//! database. //! database.
const UUID& ReportID() const { return uuid_; } const UUID& ReportID() const { return uuid_; }
@ -137,6 +146,7 @@ class CrashReportDatabase {
const base::FilePath::StringType& extension); const base::FilePath::StringType& extension);
std::unique_ptr<FileWriter> writer_; std::unique_ptr<FileWriter> writer_;
std::unique_ptr<FileReader> reader_;
ScopedRemoveFile file_remover_; ScopedRemoveFile file_remover_;
std::vector<std::unique_ptr<FileWriter>> attachment_writers_; std::vector<std::unique_ptr<FileWriter>> attachment_writers_;
std::vector<ScopedRemoveFile> attachment_removers_; std::vector<ScopedRemoveFile> attachment_removers_;
@ -163,7 +173,7 @@ class CrashReportDatabase {
//! This is not implemented on macOS or Windows. //! This is not implemented on macOS or Windows.
std::map<std::string, FileReader*> GetAttachments() const { std::map<std::string, FileReader*> GetAttachments() const {
return attachment_map_; return attachment_map_;
}; }
private: private:
friend class CrashReportDatabase; friend class CrashReportDatabase;

View File

@ -15,6 +15,7 @@
#include "client/crash_report_database.h" #include "client/crash_report_database.h"
#include <stdint.h> #include <stdint.h>
#include <sys/stat.h>
#include <sys/types.h> #include <sys/types.h>
#include <utility> #include <utility>
@ -25,6 +26,7 @@
#include "util/file/directory_reader.h" #include "util/file/directory_reader.h"
#include "util/file/filesystem.h" #include "util/file/filesystem.h"
#include "util/misc/initialization_state_dcheck.h" #include "util/misc/initialization_state_dcheck.h"
#include "util/misc/memory_sanitizer.h"
namespace crashpad { namespace crashpad {
@ -160,6 +162,34 @@ class ScopedLockFile {
DISALLOW_COPY_AND_ASSIGN(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 } // namespace
class CrashReportDatabaseGeneric : public CrashReportDatabase { class CrashReportDatabaseGeneric : public CrashReportDatabase {
@ -253,7 +283,7 @@ class CrashReportDatabaseGeneric : public CrashReportDatabase {
void RemoveAttachmentsByUUID(const UUID& uuid); void RemoveAttachmentsByUUID(const UUID& uuid);
// Reads the metadata for a report from path and returns it in report. // 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. // Wraps ReadMetadata and removes the report from the database on failure.
bool CleaningReadMetadata(const base::FilePath& path, Report* report); bool CleaningReadMetadata(const base::FilePath& path, Report* report);
@ -303,6 +333,9 @@ void CrashReportDatabase::UploadReport::InitializeAttachments() {
base::FilePath attachments_dir = base::FilePath attachments_dir =
static_cast<CrashReportDatabaseGeneric*>(database_)->AttachmentsPath( static_cast<CrashReportDatabaseGeneric*>(database_)->AttachmentsPath(
uuid); uuid);
if (!IsDirectory(attachments_dir, /*allow_symlinks=*/false)) {
return;
}
DirectoryReader reader; DirectoryReader reader;
if (!reader.Open(attachments_dir)) { if (!reader.Open(attachments_dir)) {
return; return;
@ -842,7 +875,6 @@ void CrashReportDatabaseGeneric::CleanOrphanedAttachments() {
base::FilePath root_attachments_dir(base_dir_.Append(kAttachmentsDirectory)); base::FilePath root_attachments_dir(base_dir_.Append(kAttachmentsDirectory));
DirectoryReader reader; DirectoryReader reader;
if (!reader.Open(root_attachments_dir)) { if (!reader.Open(root_attachments_dir)) {
LOG(ERROR) << "no attachments dir";
return; return;
} }
@ -883,6 +915,9 @@ void CrashReportDatabaseGeneric::CleanOrphanedAttachments() {
void CrashReportDatabaseGeneric::RemoveAttachmentsByUUID(const UUID& uuid) { void CrashReportDatabaseGeneric::RemoveAttachmentsByUUID(const UUID& uuid) {
base::FilePath attachments_dir = AttachmentsPath(uuid); base::FilePath attachments_dir = AttachmentsPath(uuid);
if (!IsDirectory(attachments_dir, /*allow_symlinks=*/false)) {
return;
}
DirectoryReader reader; DirectoryReader reader;
if (!reader.Open(attachments_dir)) { if (!reader.Open(attachments_dir)) {
return; return;
@ -899,7 +934,6 @@ void CrashReportDatabaseGeneric::RemoveAttachmentsByUUID(const UUID& uuid) {
LoggingRemoveDirectory(attachments_dir); LoggingRemoveDirectory(attachments_dir);
} }
// static
bool CrashReportDatabaseGeneric::ReadMetadata(const base::FilePath& path, bool CrashReportDatabaseGeneric::ReadMetadata(const base::FilePath& path,
Report* report) { Report* report) {
const base::FilePath metadata_path( const base::FilePath metadata_path(
@ -910,7 +944,8 @@ bool CrashReportDatabaseGeneric::ReadMetadata(const base::FilePath& path,
return false; return false;
} }
if (!report->uuid.InitializeFromString( UUID uuid;
if (!uuid.InitializeFromString(
path.BaseName().RemoveFinalExtension().value())) { path.BaseName().RemoveFinalExtension().value())) {
LOG(ERROR) << "Couldn't interpret report uuid"; LOG(ERROR) << "Couldn't interpret report uuid";
return false; return false;
@ -930,6 +965,12 @@ bool CrashReportDatabaseGeneric::ReadMetadata(const base::FilePath& path,
return false; 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->upload_attempts = metadata.upload_attempts;
report->last_upload_attempt_time = metadata.last_upload_attempt_time; report->last_upload_attempt_time = metadata.last_upload_attempt_time;
report->creation_time = metadata.creation_time; report->creation_time = metadata.creation_time;
@ -937,6 +978,7 @@ bool CrashReportDatabaseGeneric::ReadMetadata(const base::FilePath& path,
report->upload_explicitly_requested = report->upload_explicitly_requested =
(metadata.attributes & kAttributeUploadExplicitlyRequested) != 0; (metadata.attributes & kAttributeUploadExplicitlyRequested) != 0;
report->file_path = path; report->file_path = path;
report->total_size = total_size;
return true; return true;
} }
@ -966,6 +1008,11 @@ bool CrashReportDatabaseGeneric::WriteNewMetadata(const base::FilePath& path) {
} }
ReportMetadata metadata; 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); metadata.creation_time = time(nullptr);
return LoggingWriteFile(handle.get(), &metadata, sizeof(metadata)); return LoggingWriteFile(handle.get(), &metadata, sizeof(metadata));
@ -986,6 +1033,11 @@ bool CrashReportDatabaseGeneric::WriteMetadata(const base::FilePath& path,
} }
ReportMetadata metadata; 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.creation_time = report.creation_time;
metadata.last_upload_attempt_time = report.last_upload_attempt_time; metadata.last_upload_attempt_time = report.last_upload_attempt_time;
metadata.upload_attempts = report.upload_attempts; metadata.upload_attempts = report.upload_attempts;

View File

@ -29,6 +29,7 @@
#include "base/mac/scoped_nsautorelease_pool.h" #include "base/mac/scoped_nsautorelease_pool.h"
#include "base/posix/eintr_wrapper.h" #include "base/posix/eintr_wrapper.h"
#include "base/scoped_generic.h" #include "base/scoped_generic.h"
#include "base/stl_util.h"
#include "base/strings/string_piece.h" #include "base/strings/string_piece.h"
#include "base/strings/stringprintf.h" #include "base/strings/stringprintf.h"
#include "base/strings/sys_string_conversions.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. // 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]))) if (!CreateOrEnsureDirectoryExists(base_dir_.Append(kReportDirectories[i])))
return false; return false;
} }
@ -681,6 +682,14 @@ bool CrashReportDatabaseMac::ReadReportMetadataLocked(
return false; 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; return true;
} }

View File

@ -20,7 +20,6 @@
#include "test/errors.h" #include "test/errors.h"
#include "test/file.h" #include "test/file.h"
#include "test/filesystem.h" #include "test/filesystem.h"
#include "test/gtest_disabled.h"
#include "test/scoped_temp_dir.h" #include "test/scoped_temp_dir.h"
#include "util/file/file_io.h" #include "util/file/file_io.h"
#include "util/file/filesystem.h" #include "util/file/filesystem.h"
@ -31,8 +30,7 @@ namespace {
class CrashReportDatabaseTest : public testing::Test { class CrashReportDatabaseTest : public testing::Test {
public: public:
CrashReportDatabaseTest() { CrashReportDatabaseTest() {}
}
protected: protected:
// testing::Test: // testing::Test:
@ -41,9 +39,7 @@ class CrashReportDatabaseTest : public testing::Test {
ASSERT_TRUE(db_); ASSERT_TRUE(db_);
} }
void ResetDatabase() { void ResetDatabase() { db_.reset(); }
db_.reset();
}
CrashReportDatabase* db() { return db_.get(); } CrashReportDatabase* db() { return db_.get(); }
base::FilePath path() const { base::FilePath path() const {
@ -57,6 +53,12 @@ class CrashReportDatabaseTest : public testing::Test {
static constexpr char kTest[] = "test"; static constexpr char kTest[] = "test";
ASSERT_TRUE(new_report->Writer()->Write(kTest, sizeof(kTest))); 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; UUID uuid;
EXPECT_EQ(db_->FinishedWritingCrashReport(std::move(new_report), &uuid), EXPECT_EQ(db_->FinishedWritingCrashReport(std::move(new_report), &uuid),
CrashReportDatabase::kNoError); CrashReportDatabase::kNoError);
@ -101,6 +103,7 @@ class CrashReportDatabaseTest : public testing::Test {
EXPECT_EQ(report.last_upload_attempt_time, 0); EXPECT_EQ(report.last_upload_attempt_time, 0);
EXPECT_EQ(report.upload_attempts, 0); EXPECT_EQ(report.upload_attempts, 0);
EXPECT_FALSE(report.upload_explicitly_requested); EXPECT_FALSE(report.upload_explicitly_requested);
EXPECT_GE(report.total_size, 0u);
} }
void RelocateDatabase() { void RelocateDatabase() {
@ -673,7 +676,7 @@ TEST_F(CrashReportDatabaseTest, RequestUpload) {
TEST_F(CrashReportDatabaseTest, Attachments) { TEST_F(CrashReportDatabaseTest, Attachments) {
#if defined(OS_MACOSX) || defined(OS_WIN) #if defined(OS_MACOSX) || defined(OS_WIN)
// Attachments aren't supported on Mac and Windows yet. // Attachments aren't supported on Mac and Windows yet.
DISABLED_TEST(); GTEST_SKIP();
#else #else
std::unique_ptr<CrashReportDatabase::NewReport> new_report; std::unique_ptr<CrashReportDatabase::NewReport> new_report;
ASSERT_EQ(db()->PrepareNewCrashReport(&new_report), ASSERT_EQ(db()->PrepareNewCrashReport(&new_report),
@ -719,7 +722,7 @@ TEST_F(CrashReportDatabaseTest, Attachments) {
TEST_F(CrashReportDatabaseTest, OrphanedAttachments) { TEST_F(CrashReportDatabaseTest, OrphanedAttachments) {
#if defined(OS_MACOSX) || defined(OS_WIN) #if defined(OS_MACOSX) || defined(OS_WIN)
// Attachments aren't supported on Mac and Windows yet. // Attachments aren't supported on Mac and Windows yet.
DISABLED_TEST(); GTEST_SKIP();
#else #else
// TODO: This is using paths that are specific to the generic implementation // TODO: This is using paths that are specific to the generic implementation
// and will need to be generalized for Mac and Windows. // and will need to be generalized for Mac and Windows.
@ -835,6 +838,66 @@ TEST_F(CrashReportDatabaseTest, CleanBrokenDatabase) {
} }
#endif // !OS_MACOSX && !OS_WIN #endif // !OS_MACOSX && !OS_WIN
TEST_F(CrashReportDatabaseTest, TotalSize_MainReportOnly) {
std::unique_ptr<CrashReportDatabase::NewReport> new_report;
ASSERT_EQ(db()->PrepareNewCrashReport(&new_report),
CrashReportDatabase::kNoError);
// Main report.
static constexpr char main_report_data[] = "dlbvandslhb";
ASSERT_TRUE(
new_report->Writer()->Write(main_report_data, sizeof(main_report_data)));
UUID uuid;
ASSERT_EQ(db()->FinishedWritingCrashReport(std::move(new_report), &uuid),
CrashReportDatabase::kNoError);
CrashReportDatabase::Report report;
ASSERT_EQ(db()->LookUpCrashReport(uuid, &report),
CrashReportDatabase::kNoError);
EXPECT_EQ(report.total_size, sizeof(main_report_data));
}
TEST_F(CrashReportDatabaseTest, GetReportSize_RightSizeWithAttachments) {
#if defined(OS_MACOSX) || defined(OS_WIN)
// Attachments aren't supported on Mac and Windows yet.
return;
#else
std::unique_ptr<CrashReportDatabase::NewReport> new_report;
ASSERT_EQ(db()->PrepareNewCrashReport(&new_report),
CrashReportDatabase::kNoError);
// Main report.
static constexpr char main_report_data[] = "dlbvandslhb";
ASSERT_TRUE(
new_report->Writer()->Write(main_report_data, sizeof(main_report_data)));
// First attachment.
FileWriter* attachment_1 = new_report->AddAttachment("my_attachment_1");
ASSERT_NE(attachment_1, nullptr);
static constexpr char attachment_1_data[] = "vKDnidhvbiudshoihbvdsoiuh nhh";
attachment_1->Write(attachment_1_data, sizeof(attachment_1_data));
// Second attachment.
FileWriter* attachment_2 = new_report->AddAttachment("my_attachment_2");
ASSERT_NE(attachment_2, nullptr);
static constexpr char attachment_2_data[] = "sgvsvgusiyguysigfkhpmo-[";
attachment_2->Write(attachment_2_data, sizeof(attachment_2_data));
UUID uuid;
ASSERT_EQ(db()->FinishedWritingCrashReport(std::move(new_report), &uuid),
CrashReportDatabase::kNoError);
CrashReportDatabase::Report report;
ASSERT_EQ(db()->LookUpCrashReport(uuid, &report),
CrashReportDatabase::kNoError);
EXPECT_EQ(report.total_size,
sizeof(main_report_data) + sizeof(attachment_1_data) +
sizeof(attachment_2_data));
#endif
}
} // namespace } // namespace
} // namespace test } // namespace test
} // namespace crashpad } // namespace crashpad

View File

@ -458,7 +458,18 @@ void Metadata::Read() {
LOG(ERROR) << "invalid string table index"; LOG(ERROR) << "invalid string table index";
return; 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); reports_.swap(reports);

View File

@ -16,6 +16,7 @@
#define CRASHPAD_CLIENT_CRASHPAD_CLIENT_H_ #define CRASHPAD_CLIENT_CRASHPAD_CLIENT_H_
#include <map> #include <map>
#include <set>
#include <string> #include <string>
#include <vector> #include <vector>
@ -24,6 +25,7 @@
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "base/macros.h" #include "base/macros.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "util/file/file_io.h"
#include "util/misc/capture_context.h" #include "util/misc/capture_context.h"
#if defined(OS_MACOSX) #if defined(OS_MACOSX)
@ -78,6 +80,10 @@ class CrashpadClient {
//! On Fuchsia, this method binds to the exception port of the current default //! On Fuchsia, this method binds to the exception port of the current default
//! job, and starts a Crashpad handler to monitor that port. //! 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] handler The path to a Crashpad handler executable.
//! \param[in] database The path to a Crashpad database. The handler will be //! \param[in] database The path to a Crashpad database. The handler will be
//! started with this path as its `--database` argument. //! started with this path as its `--database` argument.
@ -112,6 +118,199 @@ class CrashpadClient {
bool restartable, bool restartable,
bool asynchronous_start); bool asynchronous_start);
#if defined(OS_ANDROID) || defined(OS_LINUX) || DOXYGEN
//! \brief Retrieve the socket and process ID for the handler.
//!
//! `StartHandler()` must have successfully been called before calling this
//! method.
//!
//! \param[out] sock The socket connected to the handler, if not `nullptr`.
//! \param[out] pid The handler's process ID, if not `nullptr`.
//! \return `true` on success. Otherwise `false` with a message logged.
static bool GetHandlerSocket(int* sock, pid_t* pid);
//! \brief Sets the socket to a presumably-running Crashpad handler process
//! which was started with StartHandler().
//!
//! This method installs a signal handler to request crash dumps on \a sock.
//!
//! \param[in] sock A socket connected to a Crashpad handler.
//! \param[in] pid The process ID of the handler, used to set the handler as
//! this process' ptracer. 0 indicates it is not necessary to set the
//! handler as this process' ptracer. -1 indicates that the handler's
//! process ID should be determined by communicating over the socket.
bool SetHandlerSocket(ScopedFileHandle sock, pid_t pid);
#endif // OS_ANDROID || OS_LINUX || DOXYGEN
#if defined(OS_ANDROID) || DOXYGEN
//! \brief Installs a signal handler to execute `/system/bin/app_process` and
//! load a Java class in response to a crash.
//!
//! \param[in] class_name The fully qualified class name to load, which must
//! define a `main()` method to be invoked by `app_process`. Arguments
//! will be passed to this method as though it were the Crashpad handler.
//! This class is expected to load a native library defining
//! crashpad::HandlerMain() and pass the arguments to it.
//! \param[in] env A vector of environment variables of the form `var=value`
//! defining the environment in which to execute `app_process`. If this
//! value is `nullptr`, the application's environment at the time of the
//! crash will be used.
//! \param[in] database The path to a Crashpad database. The handler will be
//! started with this path as its `--database` argument.
//! \param[in] metrics_dir The path to an already existing directory where
//! metrics files can be stored. The handler will be started with this
//! path as its `--metrics-dir` argument.
//! \param[in] url The URL of an upload server. The handler will be started
//! with this URL as its `--url` argument.
//! \param[in] annotations Process annotations to set in each crash report.
//! The handler will be started with an `--annotation` argument for each
//! element in this map.
//! \param[in] arguments Additional arguments to pass to the Crashpad handler.
//! Arguments passed in other parameters and arguments required to perform
//! the handshake are the responsibility of this method, and must not be
//! specified in this parameter.
//!
//! \return `true` on success, `false` on failure with a message logged.
bool StartJavaHandlerAtCrash(
const std::string& class_name,
const std::vector<std::string>* env,
const base::FilePath& database,
const base::FilePath& metrics_dir,
const std::string& url,
const std::map<std::string, std::string>& annotations,
const std::vector<std::string>& arguments);
//! \brief Executes `/system/bin/app_process` and loads a Java class.
//!
//! \param[in] class_name The fully qualified class name to load, which must
//! define a `main()` method to be invoked by `app_process`. Arguments
//! will be passed to this method as though it were the Crashpad handler.
//! This class is expected to load a native library defining
//! crashpad::HandlerMain() and pass the arguments to it.
//! \param[in] env A vector of environment variables of the form `var=value`
//! defining the environment in which to execute `app_process`. If this
//! value is `nullptr`, the application's current environment will be
//! used.
//! \param[in] database The path to a Crashpad database. The handler will be
//! started with this path as its `--database` argument.
//! \param[in] metrics_dir The path to an already existing directory where
//! metrics files can be stored. The handler will be started with this
//! path as its `--metrics-dir` argument.
//! \param[in] url The URL of an upload server. The handler will be started
//! with this URL as its `--url` argument.
//! \param[in] annotations Process annotations to set in each crash report.
//! The handler will be started with an `--annotation` argument for each
//! element in this map.
//! \param[in] arguments Additional arguments to pass to the Crashpad handler.
//! Arguments passed in other parameters and arguments required to perform
//! the handshake are the responsibility of this method, and must not be
//! specified in this parameter.
//! \param[in] socket The server end of a socket pair. The client end should
//! be used with an ExceptionHandlerClient.
//!
//! \return `true` on success, `false` on failure with a message logged.
static bool StartJavaHandlerForClient(
const std::string& class_name,
const std::vector<std::string>* env,
const base::FilePath& database,
const base::FilePath& metrics_dir,
const std::string& url,
const std::map<std::string, std::string>& annotations,
const std::vector<std::string>& arguments,
int socket);
//! \brief Installs a signal handler to start a Crashpad handler process by
//! loading it with `/system/bin/linker`.
//!
//! This method is only supported by Android Q+.
//!
//! \param[in] handler_trampoline The path to a Crashpad handler trampoline
//! executable, possibly located within an apk, e.g.
//! "/data/app/myapk.apk!/myabi/libcrashpad_handler_trampoline.so".
//! \param[in] handler_library The name of a library exporting the symbol
//! `CrashpadHandlerMain()`. The path to this library must be present in
//! `LD_LIBRARY_PATH`.
//! \param[in] is_64_bit `true` if \a handler_trampoline and \a
//! handler_library are 64-bit objects. They must have the same bitness.
//! \param[in] env A vector of environment variables of the form `var=value`
//! defining the environment in which to execute `app_process`. If this
//! value is `nullptr`, the application's environment at the time of the
//! crash will be used.
//! \param[in] database The path to a Crashpad database. The handler will be
//! started with this path as its `--database` argument.
//! \param[in] metrics_dir The path to an already existing directory where
//! metrics files can be stored. The handler will be started with this
//! path as its `--metrics-dir` argument.
//! \param[in] url The URL of an upload server. The handler will be started
//! with this URL as its `--url` argument.
//! \param[in] annotations Process annotations to set in each crash report.
//! The handler will be started with an `--annotation` argument for each
//! element in this map.
//! \param[in] arguments Additional arguments to pass to the Crashpad handler.
//! Arguments passed in other parameters and arguments required to perform
//! the handshake are the responsibility of this method, and must not be
//! specified in this parameter.
//!
//! \return `true` on success, `false` on failure with a message logged.
bool StartHandlerWithLinkerAtCrash(
const std::string& handler_trampoline,
const std::string& handler_library,
bool is_64_bit,
const std::vector<std::string>* env,
const base::FilePath& database,
const base::FilePath& metrics_dir,
const std::string& url,
const std::map<std::string, std::string>& annotations,
const std::vector<std::string>& arguments);
//! \brief Starts a Crashpad handler process with an initial client by loading
//! it with `/system/bin/linker`.
//!
//! This method is only supported by Android Q+.
//!
//! \param[in] handler_trampoline The path to a Crashpad handler trampoline
//! executable, possibly located within an apk, e.g.
//! "/data/app/myapk.apk!/myabi/libcrashpad_handler_trampoline.so".
//! \param[in] handler_library The name of a library exporting the symbol
//! `CrashpadHandlerMain()`. The path to this library must be present in
//! `LD_LIBRARY_PATH`.
//! \param[in] is_64_bit `true` if \a handler_trampoline and \a
//! handler_library are 64-bit objects. They must have the same bitness.
//! \param[in] env A vector of environment variables of the form `var=value`
//! defining the environment in which to execute `app_process`. If this
//! value is `nullptr`, the application's current environment will be
//! used.
//! \param[in] database The path to a Crashpad database. The handler will be
//! started with this path as its `--database` argument.
//! \param[in] metrics_dir The path to an already existing directory where
//! metrics files can be stored. The handler will be started with this
//! path as its `--metrics-dir` argument.
//! \param[in] url The URL of an upload server. The handler will be started
//! with this URL as its `--url` argument.
//! \param[in] annotations Process annotations to set in each crash report.
//! The handler will be started with an `--annotation` argument for each
//! element in this map.
//! \param[in] arguments Additional arguments to pass to the Crashpad handler.
//! Arguments passed in other parameters and arguments required to perform
//! the handshake are the responsibility of this method, and must not be
//! specified in this parameter.
//! \param[in] socket The server end of a socket pair. The client end should
//! be used with an ExceptionHandlerClient.
//!
//! \return `true` on success, `false` on failure with a message logged.
static bool StartHandlerWithLinkerForClient(
const std::string& handler_trampoline,
const std::string& handler_library,
bool is_64_bit,
const std::vector<std::string>* env,
const base::FilePath& database,
const base::FilePath& metrics_dir,
const std::string& url,
const std::map<std::string, std::string>& annotations,
const std::vector<std::string>& arguments,
int socket);
#endif // OS_ANDROID || DOXYGEN
#if defined(OS_LINUX) || defined(OS_ANDROID) || DOXYGEN #if defined(OS_LINUX) || defined(OS_ANDROID) || DOXYGEN
//! \brief Installs a signal handler to launch a handler process in reponse to //! \brief Installs a signal handler to launch a handler process in reponse to
//! a crash. //! a crash.
@ -135,7 +334,7 @@ class CrashpadClient {
//! specified in this parameter. //! specified in this parameter.
//! //!
//! \return `true` on success, `false` on failure with a message logged. //! \return `true` on success, `false` on failure with a message logged.
static bool StartHandlerAtCrash( bool StartHandlerAtCrash(
const base::FilePath& handler, const base::FilePath& handler,
const base::FilePath& database, const base::FilePath& database,
const base::FilePath& metrics_dir, const base::FilePath& metrics_dir,
@ -188,6 +387,12 @@ class CrashpadClient {
//! CaptureContext() or similar. //! CaptureContext() or similar.
static void DumpWithoutCrash(NativeCPUContext* context); 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. //! \brief The type for custom handlers installed by clients.
using FirstChanceHandler = bool (*)(int, siginfo_t*, ucontext_t*); using FirstChanceHandler = bool (*)(int, siginfo_t*, ucontext_t*);
@ -209,8 +414,38 @@ class CrashpadClient {
//! \param[in] handler The custom crash signal handler to install. //! \param[in] handler The custom crash signal handler to install.
static void SetFirstChanceExceptionHandler(FirstChanceHandler handler); static void SetFirstChanceExceptionHandler(FirstChanceHandler handler);
//! \brief Configures a set of signals that shouldn't have Crashpad signal
//! handlers installed.
//!
//! This method should be called before calling StartHandler(),
//! SetHandlerSocket(), or other methods that install Crashpad signal
//! handlers.
//!
//! \param[in] unhandled_signals The set of unhandled signals
void SetUnhandledSignals(const std::set<int>& unhandled_signals);
#endif // OS_LINUX || OS_ANDROID || DOXYGEN #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 #if defined(OS_MACOSX) || DOXYGEN
//! \brief Sets the process crash handler to a Mach service registered with //! \brief Sets the process crash handler to a Mach service registered with
//! the bootstrap server. //! the bootstrap server.
@ -384,12 +619,24 @@ class CrashpadClient {
static void UseSystemDefaultHandler(); static void UseSystemDefaultHandler();
#endif #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: private:
#if defined(OS_MACOSX) #if defined(OS_MACOSX)
base::mac::ScopedMachSendRight exception_port_; base::mac::ScopedMachSendRight exception_port_;
#elif defined(OS_WIN) #elif defined(OS_WIN)
std::wstring ipc_pipe_; std::wstring ipc_pipe_;
ScopedKernelHANDLE handler_start_thread_; ScopedKernelHANDLE handler_start_thread_;
#elif defined(OS_LINUX) || defined(OS_ANDROID)
std::set<int> unhandled_signals_;
#endif // OS_MACOSX #endif // OS_MACOSX
DISALLOW_COPY_AND_ASSIGN(CrashpadClient); DISALLOW_COPY_AND_ASSIGN(CrashpadClient);

View File

@ -15,15 +15,15 @@
#include "client/crashpad_client.h" #include "client/crashpad_client.h"
#include <lib/fdio/spawn.h> #include <lib/fdio/spawn.h>
#include <zircon/process.h> #include <lib/zx/channel.h>
#include <lib/zx/job.h>
#include <lib/zx/process.h>
#include <zircon/processargs.h> #include <zircon/processargs.h>
#include "base/fuchsia/fuchsia_logging.h" #include "base/fuchsia/fuchsia_logging.h"
#include "base/fuchsia/scoped_zx_handle.h"
#include "base/logging.h" #include "base/logging.h"
#include "base/strings/stringprintf.h" #include "base/strings/stringprintf.h"
#include "client/client_argv_handling.h" #include "client/client_argv_handling.h"
#include "util/fuchsia/system_exception_port_key.h"
namespace crashpad { namespace crashpad {
@ -43,52 +43,44 @@ bool CrashpadClient::StartHandler(
DCHECK_EQ(restartable, false); // Not used on Fuchsia. DCHECK_EQ(restartable, false); // Not used on Fuchsia.
DCHECK_EQ(asynchronous_start, false); // Not used on Fuchsia. DCHECK_EQ(asynchronous_start, false); // Not used on Fuchsia.
zx_handle_t exception_port_raw;
zx_status_t status = zx_port_create(0, &exception_port_raw);
if (status != ZX_OK) {
ZX_LOG(ERROR, status) << "zx_port_create";
return false;
}
base::ScopedZxHandle exception_port(exception_port_raw);
status = zx_task_bind_exception_port(
zx_job_default(), exception_port.get(), kSystemExceptionPortKey, 0);
if (status != ZX_OK) {
ZX_LOG(ERROR, status) << "zx_task_bind_exception_port";
return false;
}
std::vector<std::string> argv_strings = BuildHandlerArgvStrings( std::vector<std::string> argv_strings = BuildHandlerArgvStrings(
handler, database, metrics_dir, url, annotations, arguments); handler, database, metrics_dir, url, annotations, arguments);
std::vector<const char*> argv; std::vector<const char*> argv;
ConvertArgvStrings(argv_strings, &argv); StringVectorToCStringVector(argv_strings, &argv);
// Follow the same protocol as devmgr and crashlogger in Zircon (that is, // Set up handles to send to the spawned process:
// process handle as handle 0, with type USER0, exception port handle as // 0. PA_USER0 job
// handle 1, also with type PA_USER0) so that it's trivial to replace // 1. PA_USER0 exception channel
// crashlogger with crashpad_handler. The exception port is passed on, so //
// released here. Currently it is assumed that this process's default job // Currently it is assumed that this process's default job handle is the
// handle is the exception port that should be monitored. In the future, it // exception channel that should be monitored. In the future, it might be
// might be useful for this to be configurable by the client. // useful for this to be configurable by the client.
constexpr size_t kActionCount = 2; zx::job job;
fdio_spawn_action_t actions[] = { zx_status_t status =
{.action = FDIO_SPAWN_ACTION_ADD_HANDLE, zx::job::default_job()->duplicate(ZX_RIGHT_SAME_RIGHTS, &job);
.h = {.id = PA_HND(PA_USER0, 0), .handle = ZX_HANDLE_INVALID}},
{.action = FDIO_SPAWN_ACTION_ADD_HANDLE,
.h = {.id = PA_HND(PA_USER0, 1), .handle = ZX_HANDLE_INVALID}},
};
status = zx_handle_duplicate(
zx_job_default(), ZX_RIGHT_SAME_RIGHTS, &actions[0].h.handle);
if (status != ZX_OK) { if (status != ZX_OK) {
ZX_LOG(ERROR, status) << "zx_handle_duplicate"; ZX_LOG(ERROR, status) << "zx_handle_duplicate";
return false; 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]; 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 // TODO(scottmg): https://crashpad.chromium.org/bug/196, FDIO_SPAWN_CLONE_ALL
// is useful during bringup, but should probably be made minimal for real // is useful during bringup, but should probably be made minimal for real
// usage. // usage.
@ -99,9 +91,8 @@ bool CrashpadClient::StartHandler(
nullptr, nullptr,
kActionCount, kActionCount,
actions, actions,
&child_raw, child.reset_and_get_address(),
error_message); error_message);
base::ScopedZxHandle child(child_raw);
if (status != ZX_OK) { if (status != ZX_OK) {
ZX_LOG(ERROR, status) << "fdio_spawn_etc: " << error_message; ZX_LOG(ERROR, status) << "fdio_spawn_etc: " << error_message;
return false; return false;

View File

@ -0,0 +1,228 @@
// Copyright 2020 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "client/crashpad_client.h"
#include <unistd.h>
#include <ios>
#include "base/logging.h"
#include "base/mac/mach_logging.h"
#include "base/mac/scoped_mach_port.h"
#include "base/stl_util.h"
#include "snapshot/ios/process_snapshot_ios.h"
#include "util/ios/exception_processor.h"
#include "util/ios/ios_system_data_collector.h"
#include "util/mach/exc_server_variants.h"
#include "util/mach/exception_ports.h"
#include "util/mach/mach_extensions.h"
#include "util/mach/mach_message.h"
#include "util/mach/mach_message_server.h"
#include "util/misc/initialization_state_dcheck.h"
#include "util/posix/signals.h"
#include "util/thread/thread.h"
namespace crashpad {
namespace {
// A base class for signal handler and Mach exception server.
class CrashHandler : public Thread, public UniversalMachExcServer::Interface {
public:
static CrashHandler* Get() {
static CrashHandler* instance = new CrashHandler();
return instance;
}
void Initialize() {
INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
InstallMachExceptionHandler();
CHECK(Signals::InstallHandler(SIGABRT, CatchSignal, 0, &old_action_));
INITIALIZATION_STATE_SET_VALID(initialized_);
}
void DumpWithoutCrash(NativeCPUContext* context) {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
mach_exception_data_type_t code[2] = {};
static constexpr int kSimulatedException = -1;
HandleMachException(MACH_EXCEPTION_CODES,
mach_thread_self(),
kSimulatedException,
code,
base::size(code),
MACHINE_THREAD_STATE,
reinterpret_cast<ConstThreadState>(context),
MACHINE_THREAD_STATE_COUNT);
}
private:
CrashHandler() = default;
void InstallMachExceptionHandler() {
exception_port_.reset(NewMachPort(MACH_PORT_RIGHT_RECEIVE));
CHECK(exception_port_.is_valid());
kern_return_t kr = mach_port_insert_right(mach_task_self(),
exception_port_.get(),
exception_port_.get(),
MACH_MSG_TYPE_MAKE_SEND);
MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_port_insert_right";
// TODO: Use SwapExceptionPort instead and put back EXC_MASK_BREAKPOINT.
const exception_mask_t mask =
ExcMaskAll() &
~(EXC_MASK_EMULATION | EXC_MASK_SOFTWARE | EXC_MASK_BREAKPOINT |
EXC_MASK_RPC_ALERT | EXC_MASK_GUARD);
ExceptionPorts exception_ports(ExceptionPorts::kTargetTypeTask, TASK_NULL);
exception_ports.GetExceptionPorts(mask, &original_handlers_);
exception_ports.SetExceptionPort(
mask,
exception_port_.get(),
EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES,
MACHINE_THREAD_STATE);
Start();
}
// Thread:
void ThreadMain() override {
UniversalMachExcServer universal_mach_exc_server(this);
while (true) {
mach_msg_return_t mr =
MachMessageServer::Run(&universal_mach_exc_server,
exception_port_.get(),
MACH_MSG_OPTION_NONE,
MachMessageServer::kPersistent,
MachMessageServer::kReceiveLargeIgnore,
kMachMessageTimeoutWaitIndefinitely);
MACH_CHECK(mr == MACH_SEND_INVALID_DEST, mr) << "MachMessageServer::Run";
}
}
// UniversalMachExcServer::Interface:
kern_return_t CatchMachException(exception_behavior_t behavior,
exception_handler_t exception_port,
thread_t thread,
task_t task,
exception_type_t exception,
const mach_exception_data_type_t* code,
mach_msg_type_number_t code_count,
thread_state_flavor_t* flavor,
ConstThreadState old_state,
mach_msg_type_number_t old_state_count,
thread_state_t new_state,
mach_msg_type_number_t* new_state_count,
const mach_msg_trailer_t* trailer,
bool* destroy_complex_request) override {
*destroy_complex_request = true;
// TODO(justincohen): Forward exceptions to original_handlers_ with
// UniversalExceptionRaise.
// iOS shouldn't have any child processes, but just in case, those will
// inherit the task exception ports, and this process isnt prepared to
// handle them
if (task != mach_task_self()) {
LOG(WARNING) << "task 0x" << std::hex << task << " != 0x"
<< mach_task_self();
return KERN_FAILURE;
}
HandleMachException(behavior,
thread,
exception,
code,
code_count,
*flavor,
old_state,
old_state_count);
// Respond with KERN_FAILURE so the system will continue to handle this
// exception as a crash.
return KERN_FAILURE;
}
void HandleMachException(exception_behavior_t behavior,
thread_t thread,
exception_type_t exception,
const mach_exception_data_type_t* code,
mach_msg_type_number_t code_count,
thread_state_flavor_t flavor,
ConstThreadState old_state,
mach_msg_type_number_t old_state_count) {
// TODO(justincohen): This is incomplete.
ProcessSnapshotIOS process_snapshot;
process_snapshot.Initialize(system_data_);
process_snapshot.SetExceptionFromMachException(behavior,
thread,
exception,
code,
code_count,
flavor,
old_state,
old_state_count);
}
// The signal handler installed at OS-level.
static void CatchSignal(int signo, siginfo_t* siginfo, void* context) {
Get()->HandleAndReraiseSignal(
signo, siginfo, reinterpret_cast<ucontext_t*>(context));
}
void HandleAndReraiseSignal(int signo,
siginfo_t* siginfo,
ucontext_t* context) {
// TODO(justincohen): This is incomplete.
ProcessSnapshotIOS process_snapshot;
process_snapshot.Initialize(system_data_);
process_snapshot.SetExceptionFromSignal(siginfo, context);
// Always call system handler.
Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, &old_action_);
}
base::mac::ScopedMachReceiveRight exception_port_;
ExceptionPorts::ExceptionHandlerVector original_handlers_;
struct sigaction old_action_ = {};
IOSSystemDataCollector system_data_;
InitializationStateDcheck initialized_;
DISALLOW_COPY_AND_ASSIGN(CrashHandler);
};
} // namespace
CrashpadClient::CrashpadClient() {}
CrashpadClient::~CrashpadClient() {}
void CrashpadClient::StartCrashpadInProcessHandler() {
InstallObjcExceptionPreprocessor();
CrashHandler* crash_handler = CrashHandler::Get();
DCHECK(crash_handler);
crash_handler->Initialize();
}
// static
void CrashpadClient::DumpWithoutCrash(NativeCPUContext* context) {
CrashHandler* crash_handler = CrashHandler::Get();
DCHECK(crash_handler);
crash_handler->DumpWithoutCrash(context);
}
} // namespace crashpad

View File

@ -0,0 +1,73 @@
// Copyright 2020 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "client/crashpad_client.h"
#import <Foundation/Foundation.h>
#include <vector>
#include "gtest/gtest.h"
#include "testing/platform_test.h"
namespace crashpad {
namespace test {
namespace {
using CrashpadIOSClient = PlatformTest;
TEST_F(CrashpadIOSClient, DumpWithoutCrash) {
CrashpadClient client;
client.StartCrashpadInProcessHandler();
NativeCPUContext context;
#if defined(ARCH_CPU_X86_64)
CaptureContext(&context);
#elif defined(ARCH_CPU_ARM64)
// TODO(justincohen): Implement CaptureContext for ARM64.
mach_msg_type_number_t thread_state_count = MACHINE_THREAD_STATE_COUNT;
kern_return_t kr =
thread_get_state(mach_thread_self(),
MACHINE_THREAD_STATE,
reinterpret_cast<thread_state_t>(&context),
&thread_state_count);
ASSERT_EQ(kr, KERN_SUCCESS);
#endif
client.DumpWithoutCrash(&context);
}
// This test is covered by a similar XCUITest, but for development purposes
// it's sometimes easier and faster to run as a gtest. However, there's no
// way to correctly run this as a gtest. Leave the test here, disabled, for use
// during development only.
TEST_F(CrashpadIOSClient, DISABLED_ThrowNSException) {
CrashpadClient client;
client.StartCrashpadInProcessHandler();
[NSException raise:@"GtestNSException" format:@"ThrowException"];
}
// This test is covered by a similar XCUITest, but for development purposes
// it's sometimes easier and faster to run as a gtest. However, there's no
// way to correctly run this as a gtest. Leave the test here, disabled, for use
// during development only.
TEST_F(CrashpadIOSClient, DISABLED_ThrowException) {
CrashpadClient client;
client.StartCrashpadInProcessHandler();
std::vector<int> empty_vector;
empty_vector.at(42);
}
} // namespace
} // namespace test
} // namespace crashpad

View File

@ -16,6 +16,7 @@
#include <fcntl.h> #include <fcntl.h>
#include <stdlib.h> #include <stdlib.h>
#include <sys/prctl.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/syscall.h> #include <sys/syscall.h>
#include <sys/types.h> #include <sys/types.h>
@ -25,10 +26,14 @@
#include "base/logging.h" #include "base/logging.h"
#include "base/strings/stringprintf.h" #include "base/strings/stringprintf.h"
#include "client/client_argv_handling.h" #include "client/client_argv_handling.h"
#include "third_party/lss/lss.h"
#include "util/file/file_io.h" #include "util/file/file_io.h"
#include "util/file/filesystem.h"
#include "util/linux/exception_handler_client.h" #include "util/linux/exception_handler_client.h"
#include "util/linux/exception_information.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/scoped_pr_set_ptracer.h"
#include "util/linux/socket.h"
#include "util/misc/from_pointer_cast.h" #include "util/misc/from_pointer_cast.h"
#include "util/posix/double_fork_and_exec.h" #include "util/posix/double_fork_and_exec.h"
#include "util/posix/signals.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); 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); return base::StringPrintf("--%s=%p", name.c_str(), addr);
} }
#if defined(OS_ANDROID)
std::vector<std::string> BuildAppProcessArgs(
const std::string& class_name,
const base::FilePath& database,
const base::FilePath& metrics_dir,
const std::string& url,
const std::map<std::string, std::string>& annotations,
const std::vector<std::string>& arguments,
int socket) {
#if defined(ARCH_CPU_64_BITS)
static constexpr char kAppProcess[] = "/system/bin/app_process64";
#else
static constexpr char kAppProcess[] = "/system/bin/app_process32";
#endif
std::vector<std::string> argv;
argv.push_back(kAppProcess);
argv.push_back("/system/bin");
argv.push_back("--application");
argv.push_back(class_name);
std::vector<std::string> handler_argv =
BuildHandlerArgvStrings(base::FilePath(kAppProcess),
database,
metrics_dir,
url,
annotations,
arguments);
if (socket != kInvalidFileHandle) {
handler_argv.push_back(FormatArgumentInt("initial-client-fd", socket));
}
argv.insert(argv.end(), handler_argv.begin(), handler_argv.end());
return argv;
}
std::vector<std::string> BuildArgsToLaunchWithLinker(
const std::string& handler_trampoline,
const std::string& handler_library,
bool is_64_bit,
const base::FilePath& database,
const base::FilePath& metrics_dir,
const std::string& url,
const std::map<std::string, std::string>& annotations,
const std::vector<std::string>& arguments,
int socket) {
std::vector<std::string> argv;
if (is_64_bit) {
argv.push_back("/system/bin/linker64");
} else {
argv.push_back("/system/bin/linker");
}
argv.push_back(handler_trampoline);
argv.push_back(handler_library);
std::vector<std::string> handler_argv = BuildHandlerArgvStrings(
base::FilePath(), database, metrics_dir, url, annotations, arguments);
if (socket != kInvalidFileHandle) {
handler_argv.push_back(FormatArgumentInt("initial-client-fd", socket));
}
argv.insert(argv.end(), handler_argv.begin() + 1, handler_argv.end());
return argv;
}
#endif // OS_ANDROID
// A base class for Crashpad signal handler implementations.
class SignalHandler { class SignalHandler {
public: public:
virtual void HandleCrashFatal(int signo, // Returns the currently installed signal hander. May be `nullptr` if no
siginfo_t* siginfo, // handler has been installed.
void* context) = 0; static SignalHandler* Get() { return handler_; }
virtual bool HandleCrashNonFatal(int signo,
siginfo_t* siginfo, // Disables any installed Crashpad signal handler for the calling thread. If a
void* context) = 0; // 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) { void SetFirstChanceHandler(CrashpadClient::FirstChanceHandler handler) {
first_chance_handler_ = handler; first_chance_handler_ = handler;
} }
protected: // The base implementation for all signal handlers, suitable for calling
SignalHandler() = default; // directly to simulate signal delivery.
~SignalHandler() = default; bool HandleCrash(int signo, siginfo_t* siginfo, void* context) {
if (disabled_for_thread_) {
CrashpadClient::FirstChanceHandler first_chance_handler_ = nullptr; return false;
};
// Launches a single use handler to snapshot this process.
class LaunchAtCrashHandler : public SignalHandler {
public:
static LaunchAtCrashHandler* Get() {
static LaunchAtCrashHandler* instance = new LaunchAtCrashHandler();
return instance;
} }
bool Initialize(std::vector<std::string>* argv_in) {
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_ && if (first_chance_handler_ &&
first_chance_handler_( first_chance_handler_(
signo, siginfo, static_cast<ucontext_t*>(context))) { signo, siginfo, static_cast<ucontext_t*>(context))) {
@ -98,29 +155,100 @@ class LaunchAtCrashHandler : public SignalHandler {
exception_information_.context_address = exception_information_.context_address =
FromPointerCast<decltype(exception_information_.context_address)>( FromPointerCast<decltype(exception_information_.context_address)>(
context); context);
exception_information_.thread_id = syscall(SYS_gettid); exception_information_.thread_id = sys_gettid();
ScopedPrSetPtracer set_ptracer(getpid(), /* may_log= */ false); ScopedPrSetDumpable set_dumpable(false);
HandleCrashImpl();
return false;
}
protected:
SignalHandler() = default;
bool Install(const std::set<int>* unhandled_signals) {
DCHECK(!handler_);
handler_ = this;
return Signals::InstallCrashHandlers(
HandleOrReraiseSignal, 0, &old_actions_, unhandled_signals);
}
const ExceptionInformation& GetExceptionInfo() {
return exception_information_;
}
virtual void HandleCrashImpl() = 0;
private:
// The signal handler installed at OS-level.
static void HandleOrReraiseSignal(int signo,
siginfo_t* siginfo,
void* context) {
if (handler_->HandleCrash(signo, siginfo, context)) {
return;
}
Signals::RestoreHandlerAndReraiseSignalOnReturn(
siginfo, handler_->old_actions_.ActionForSignal(signo));
}
Signals::OldActions old_actions_ = {};
ExceptionInformation exception_information_ = {};
CrashpadClient::FirstChanceHandler first_chance_handler_ = nullptr;
static SignalHandler* handler_;
static thread_local bool disabled_for_thread_;
DISALLOW_COPY_AND_ASSIGN(SignalHandler);
};
SignalHandler* SignalHandler::handler_ = nullptr;
thread_local bool SignalHandler::disabled_for_thread_ = false;
// Launches a single use handler to snapshot this process.
class LaunchAtCrashHandler : public SignalHandler {
public:
static LaunchAtCrashHandler* Get() {
static LaunchAtCrashHandler* instance = new LaunchAtCrashHandler();
return instance;
}
bool Initialize(std::vector<std::string>* argv_in,
const std::vector<std::string>* envp,
const std::set<int>* unhandled_signals) {
argv_strings_.swap(*argv_in);
if (envp) {
envp_strings_ = *envp;
StringVectorToCStringVector(envp_strings_, &envp_);
set_envp_ = true;
}
argv_strings_.push_back(FormatArgumentAddress("trace-parent-with-exception",
&GetExceptionInfo()));
StringVectorToCStringVector(argv_strings_, &argv_);
return Install(unhandled_signals);
}
void HandleCrashImpl() override {
ScopedPrSetPtracer set_ptracer(sys_getpid(), /* may_log= */ false);
pid_t pid = fork(); pid_t pid = fork();
if (pid < 0) { if (pid < 0) {
return false; return;
} }
if (pid == 0) { if (pid == 0) {
if (set_envp_) {
execve(argv_[0],
const_cast<char* const*>(argv_.data()),
const_cast<char* const*>(envp_.data()));
} else {
execv(argv_[0], const_cast<char* const*>(argv_.data())); execv(argv_[0], const_cast<char* const*>(argv_.data()));
}
_exit(EXIT_FAILURE); _exit(EXIT_FAILURE);
} }
int status; int status;
waitpid(pid, &status, 0); 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: private:
@ -128,22 +256,107 @@ class LaunchAtCrashHandler : public SignalHandler {
~LaunchAtCrashHandler() = delete; ~LaunchAtCrashHandler() = delete;
static void HandleCrash(int signo, siginfo_t* siginfo, void* context) {
auto state = Get();
state->HandleCrashFatal(signo, siginfo, context);
}
std::vector<std::string> argv_strings_; std::vector<std::string> argv_strings_;
std::vector<const char*> argv_; std::vector<const char*> argv_;
ExceptionInformation exception_information_; std::vector<std::string> envp_strings_;
std::vector<const char*> envp_;
bool set_envp_ = false;
DISALLOW_COPY_AND_ASSIGN(LaunchAtCrashHandler); DISALLOW_COPY_AND_ASSIGN(LaunchAtCrashHandler);
}; };
// A pointer to the currently installed crash signal handler. This allows class RequestCrashDumpHandler : public SignalHandler {
// the static method CrashpadClient::DumpWithoutCrashing to simulate a crash public:
// using the currently configured crash handling strategy. static RequestCrashDumpHandler* Get() {
static SignalHandler* g_crash_handler; static RequestCrashDumpHandler* instance = new RequestCrashDumpHandler();
return instance;
}
// pid < 0 indicates the handler pid should be determined by communicating
// over the socket.
// pid == 0 indicates it is not necessary to set the handler as this process'
// ptracer. e.g. if the handler has CAP_SYS_PTRACE or if this process is in a
// user namespace and the handler's uid matches the uid of the process that
// created the namespace.
// pid > 0 directly indicates what the handler's pid is expected to be, so
// retrieving this information from the handler is not necessary.
bool Initialize(ScopedFileHandle sock,
pid_t pid,
const std::set<int>* unhandled_signals) {
ExceptionHandlerClient client(sock.get(), true);
if (pid < 0) {
ucred creds;
if (!client.GetHandlerCredentials(&creds)) {
return false;
}
pid = creds.pid;
}
if (pid > 0 && prctl(PR_SET_PTRACER, pid, 0, 0, 0) != 0) {
PLOG(WARNING) << "prctl";
// TODO(jperaza): If this call to set the ptracer failed, it might be
// possible to try again just before a dump request, in case the
// environment has changed. Revisit ExceptionHandlerClient::SetPtracer()
// and consider saving the result of this call in ExceptionHandlerClient
// or as a member in this signal handler. ExceptionHandlerClient hasn't
// been responsible for maintaining state and a new ExceptionHandlerClient
// has been constructed as a local whenever a client needs to communicate
// with the handler. ExceptionHandlerClient lifetimes and ownership will
// need to be reconsidered if it becomes responsible for state.
}
sock_to_handler_.reset(sock.release());
handler_pid_ = pid;
return Install(unhandled_signals);
}
bool GetHandlerSocket(int* sock, pid_t* pid) {
if (!sock_to_handler_.is_valid()) {
return false;
}
if (sock) {
*sock = sock_to_handler_.get();
}
if (pid) {
*pid = handler_pid_;
}
return true;
}
void HandleCrashImpl() override {
ExceptionHandlerProtocol::ClientInformation info = {};
info.exception_information_address =
FromPointerCast<VMAddress>(&GetExceptionInfo());
#if defined(OS_CHROMEOS)
info.crash_loop_before_time = crash_loop_before_time_;
#endif
ExceptionHandlerClient client(sock_to_handler_.get(), true);
client.RequestCrashDump(info);
}
#if defined(OS_CHROMEOS)
void SetCrashLoopBefore(uint64_t crash_loop_before_time) {
crash_loop_before_time_ = crash_loop_before_time;
}
#endif
private:
RequestCrashDumpHandler() = default;
~RequestCrashDumpHandler() = delete;
ScopedFileHandle sock_to_handler_;
pid_t handler_pid_ = -1;
#if defined(OS_CHROMEOS)
// An optional UNIX timestamp passed to us from Chrome.
// This will pass to crashpad_handler and then to Chrome OS crash_reporter.
// This should really be a time_t, but it's basically an opaque value (we
// don't anything with it except pass it along).
uint64_t crash_loop_before_time_ = 0;
#endif
DISALLOW_COPY_AND_ASSIGN(RequestCrashDumpHandler);
};
} // namespace } // namespace
@ -160,14 +373,134 @@ bool CrashpadClient::StartHandler(
const std::vector<std::string>& arguments, const std::vector<std::string>& arguments,
bool restartable, bool restartable,
bool asynchronous_start) { bool asynchronous_start) {
// TODO(jperaza): Implement this after the Android/Linux ExceptionHandlerSever DCHECK(!asynchronous_start);
// supports accepting new connections.
// https://crashpad.chromium.org/bug/30 ScopedFileHandle client_sock, handler_sock;
NOTREACHED(); if (!UnixCredentialSocket::CreateCredentialSocketpair(&client_sock,
&handler_sock)) {
return false; return false;
}
std::vector<std::string> argv = BuildHandlerArgvStrings(
handler, database, metrics_dir, url, annotations, arguments);
argv.push_back(FormatArgumentInt("initial-client-fd", handler_sock.get()));
argv.push_back("--shared-client-connection");
if (!DoubleForkAndExec(argv, nullptr, handler_sock.get(), false, nullptr)) {
return false;
}
pid_t handler_pid = -1;
if (!IsRegularFile(base::FilePath("/proc/sys/kernel/yama/ptrace_scope"))) {
handler_pid = 0;
}
auto signal_handler = RequestCrashDumpHandler::Get();
return signal_handler->Initialize(
std::move(client_sock), handler_pid, &unhandled_signals_);
}
#if defined(OS_ANDROID) || defined(OS_LINUX)
// static
bool CrashpadClient::GetHandlerSocket(int* sock, pid_t* pid) {
auto signal_handler = RequestCrashDumpHandler::Get();
return signal_handler->GetHandlerSocket(sock, pid);
}
bool CrashpadClient::SetHandlerSocket(ScopedFileHandle sock, pid_t pid) {
auto signal_handler = RequestCrashDumpHandler::Get();
return signal_handler->Initialize(std::move(sock), pid, &unhandled_signals_);
}
#endif // OS_ANDROID || OS_LINUX
#if defined(OS_ANDROID)
bool CrashpadClient::StartJavaHandlerAtCrash(
const std::string& class_name,
const std::vector<std::string>* env,
const base::FilePath& database,
const base::FilePath& metrics_dir,
const std::string& url,
const std::map<std::string, std::string>& annotations,
const std::vector<std::string>& arguments) {
std::vector<std::string> argv = BuildAppProcessArgs(class_name,
database,
metrics_dir,
url,
annotations,
arguments,
kInvalidFileHandle);
auto signal_handler = LaunchAtCrashHandler::Get();
return signal_handler->Initialize(&argv, env, &unhandled_signals_);
} }
// static // static
bool CrashpadClient::StartJavaHandlerForClient(
const std::string& class_name,
const std::vector<std::string>* env,
const base::FilePath& database,
const base::FilePath& metrics_dir,
const std::string& url,
const std::map<std::string, std::string>& annotations,
const std::vector<std::string>& arguments,
int socket) {
std::vector<std::string> argv = BuildAppProcessArgs(
class_name, database, metrics_dir, url, annotations, arguments, socket);
return DoubleForkAndExec(argv, env, socket, false, nullptr);
}
bool CrashpadClient::StartHandlerWithLinkerAtCrash(
const std::string& handler_trampoline,
const std::string& handler_library,
bool is_64_bit,
const std::vector<std::string>* env,
const base::FilePath& database,
const base::FilePath& metrics_dir,
const std::string& url,
const std::map<std::string, std::string>& annotations,
const std::vector<std::string>& arguments) {
std::vector<std::string> argv =
BuildArgsToLaunchWithLinker(handler_trampoline,
handler_library,
is_64_bit,
database,
metrics_dir,
url,
annotations,
arguments,
kInvalidFileHandle);
auto signal_handler = LaunchAtCrashHandler::Get();
return signal_handler->Initialize(&argv, env, &unhandled_signals_);
}
// static
bool CrashpadClient::StartHandlerWithLinkerForClient(
const std::string& handler_trampoline,
const std::string& handler_library,
bool is_64_bit,
const std::vector<std::string>* env,
const base::FilePath& database,
const base::FilePath& metrics_dir,
const std::string& url,
const std::map<std::string, std::string>& annotations,
const std::vector<std::string>& arguments,
int socket) {
std::vector<std::string> argv =
BuildArgsToLaunchWithLinker(handler_trampoline,
handler_library,
is_64_bit,
database,
metrics_dir,
url,
annotations,
arguments,
socket);
return DoubleForkAndExec(argv, env, socket, false, nullptr);
}
#endif
bool CrashpadClient::StartHandlerAtCrash( bool CrashpadClient::StartHandlerAtCrash(
const base::FilePath& handler, const base::FilePath& handler,
const base::FilePath& database, const base::FilePath& database,
@ -179,12 +512,7 @@ bool CrashpadClient::StartHandlerAtCrash(
handler, database, metrics_dir, url, annotations, arguments); handler, database, metrics_dir, url, annotations, arguments);
auto signal_handler = LaunchAtCrashHandler::Get(); auto signal_handler = LaunchAtCrashHandler::Get();
if (signal_handler->Initialize(&argv)) { return signal_handler->Initialize(&argv, nullptr, &unhandled_signals_);
DCHECK(!g_crash_handler);
g_crash_handler = signal_handler;
return true;
}
return false;
} }
// static // static
@ -199,14 +527,17 @@ bool CrashpadClient::StartHandlerForClient(
std::vector<std::string> argv = BuildHandlerArgvStrings( std::vector<std::string> argv = BuildHandlerArgvStrings(
handler, database, metrics_dir, url, annotations, arguments); 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 // static
void CrashpadClient::DumpWithoutCrash(NativeCPUContext* context) { void CrashpadClient::DumpWithoutCrash(NativeCPUContext* context) {
DCHECK(g_crash_handler); if (!SignalHandler::Get()) {
DLOG(ERROR) << "Crashpad isn't enabled";
return;
}
#if defined(ARCH_CPU_ARMEL) #if defined(ARCH_CPU_ARMEL)
memset(context->uc_regspace, 0, sizeof(context->uc_regspace)); 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_signo = Signals::kSimulatedSigno;
siginfo.si_errno = 0; siginfo.si_errno = 0;
siginfo.si_code = 0; siginfo.si_code = 0;
g_crash_handler->HandleCrashNonFatal( SignalHandler::Get()->HandleCrash(
siginfo.si_signo, &siginfo, reinterpret_cast<void*>(context)); siginfo.si_signo, &siginfo, reinterpret_cast<void*>(context));
} }
// static
void CrashpadClient::CrashWithoutDump(const std::string& message) {
SignalHandler::DisableForThread();
LOG(FATAL) << message;
}
// static // static
void CrashpadClient::SetFirstChanceExceptionHandler( void CrashpadClient::SetFirstChanceExceptionHandler(
FirstChanceHandler handler) { FirstChanceHandler handler) {
DCHECK(g_crash_handler); DCHECK(SignalHandler::Get());
g_crash_handler->SetFirstChanceHandler(handler); SignalHandler::Get()->SetFirstChanceHandler(handler);
} }
void CrashpadClient::SetUnhandledSignals(const std::set<int>& signals) {
DCHECK(!SignalHandler::Get());
unhandled_signals_ = signals;
}
#if defined(OS_CHROMEOS)
// static
void CrashpadClient::SetCrashLoopBefore(uint64_t crash_loop_before_time) {
auto request_crash_dump_handler = RequestCrashDumpHandler::Get();
request_crash_dump_handler->SetCrashLoopBefore(crash_loop_before_time);
}
#endif
} // namespace crashpad } // namespace crashpad

View File

@ -14,8 +14,8 @@
#include "client/crashpad_client.h" #include "client/crashpad_client.h"
#include <dlfcn.h>
#include <stdlib.h> #include <stdlib.h>
#include <sys/socket.h>
#include <sys/syscall.h> #include <sys/syscall.h>
#include <sys/types.h> #include <sys/types.h>
#include <unistd.h> #include <unistd.h>
@ -37,76 +37,98 @@
#include "util/file/filesystem.h" #include "util/file/filesystem.h"
#include "util/linux/exception_handler_client.h" #include "util/linux/exception_handler_client.h"
#include "util/linux/exception_information.h" #include "util/linux/exception_information.h"
#include "util/linux/socket.h"
#include "util/misc/address_types.h" #include "util/misc/address_types.h"
#include "util/misc/from_pointer_cast.h" #include "util/misc/from_pointer_cast.h"
#include "util/posix/signals.h" #include "util/posix/signals.h"
#if defined(OS_ANDROID)
#include <android/set_abort_message.h>
#include "dlfcn_internal.h"
// Normally this comes from set_abort_message.h, but only at API level 21.
extern "C" void android_set_abort_message(const char* msg)
__attribute__((weak));
#endif
namespace crashpad { namespace crashpad {
namespace test { namespace test {
namespace { namespace {
struct StartHandlerForSelfTestOptions {
bool start_handler_at_crash;
bool simulate_crash;
bool set_first_chance_handler;
};
class StartHandlerForSelfTest
: public testing::TestWithParam<std::tuple<bool, bool, bool>> {
public:
StartHandlerForSelfTest() = default;
~StartHandlerForSelfTest() = default;
void SetUp() override {
std::tie(options_.start_handler_at_crash,
options_.simulate_crash,
options_.set_first_chance_handler) = GetParam();
}
const StartHandlerForSelfTestOptions& Options() const { return options_; }
private:
StartHandlerForSelfTestOptions options_;
DISALLOW_COPY_AND_ASSIGN(StartHandlerForSelfTest);
};
bool HandleCrashSuccessfully(int, siginfo_t*, ucontext_t*) { bool HandleCrashSuccessfully(int, siginfo_t*, ucontext_t*) {
return true; return true;
} }
TEST(CrashpadClient, SimulateCrash) { bool InstallHandler(CrashpadClient* client,
ScopedTempDir temp_dir; bool start_at_crash,
const base::FilePath& handler_path,
base::FilePath handler_path = TestPaths::Executable().DirName().Append( const base::FilePath& database_path) {
FILE_PATH_LITERAL("crashpad_handler")); return start_at_crash
? client->StartHandlerAtCrash(handler_path,
crashpad::CrashpadClient client; database_path,
ASSERT_TRUE(client.StartHandlerAtCrash(handler_path,
base::FilePath(temp_dir.path()),
base::FilePath(), base::FilePath(),
"", "",
std::map<std::string, std::string>(), std::map<std::string, std::string>(),
std::vector<std::string>())); std::vector<std::string>())
: client->StartHandler(handler_path,
auto database = database_path,
CrashReportDatabase::InitializeWithoutCreating(temp_dir.path()); base::FilePath(),
ASSERT_TRUE(database); "",
std::map<std::string, std::string>(),
{ std::vector<std::string>(),
CrashpadClient::SetFirstChanceExceptionHandler(HandleCrashSuccessfully); false,
false);
CRASHPAD_SIMULATE_CRASH();
std::vector<CrashReportDatabase::Report> reports;
ASSERT_EQ(database->GetPendingReports(&reports),
CrashReportDatabase::kNoError);
EXPECT_EQ(reports.size(), 0u);
reports.clear();
ASSERT_EQ(database->GetCompletedReports(&reports),
CrashReportDatabase::kNoError);
EXPECT_EQ(reports.size(), 0u);
}
{
CrashpadClient::SetFirstChanceExceptionHandler(nullptr);
CRASHPAD_SIMULATE_CRASH();
std::vector<CrashReportDatabase::Report> reports;
ASSERT_EQ(database->GetPendingReports(&reports),
CrashReportDatabase::kNoError);
EXPECT_EQ(reports.size(), 1u);
reports.clear();
ASSERT_EQ(database->GetCompletedReports(&reports),
CrashReportDatabase::kNoError);
EXPECT_EQ(reports.size(), 0u);
}
} }
constexpr char kTestAnnotationName[] = "name_of_annotation"; constexpr char kTestAnnotationName[] = "name_of_annotation";
constexpr char kTestAnnotationValue[] = "value_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) { void ValidateDump(const CrashReportDatabase::UploadReport* report) {
ProcessSnapshotMinidump minidump_snapshot; ProcessSnapshotMinidump minidump_snapshot;
ASSERT_TRUE(minidump_snapshot.Initialize(report->Reader())); 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 ModuleSnapshot* module : minidump_snapshot.Modules()) {
for (const AnnotationSnapshot& annotation : module->AnnotationObjects()) { for (const AnnotationSnapshot& annotation : module->AnnotationObjects()) {
if (static_cast<Annotation::Type>(annotation.type) != if (static_cast<Annotation::Type>(annotation.type) !=
@ -126,7 +148,7 @@ void ValidateDump(const CrashReportDatabase::UploadReport* report) {
ADD_FAILURE(); ADD_FAILURE();
} }
CRASHPAD_CHILD_TEST_MAIN(StartHandlerAtCrashChild) { CRASHPAD_CHILD_TEST_MAIN(StartHandlerForSelfTestChild) {
FileHandle in = StdioFileHandle(StdioStream::kStandardInput); FileHandle in = StdioFileHandle(StdioStream::kStandardInput);
VMSize temp_dir_length; VMSize temp_dir_length;
@ -135,6 +157,9 @@ CRASHPAD_CHILD_TEST_MAIN(StartHandlerAtCrashChild) {
std::string temp_dir(temp_dir_length, '\0'); std::string temp_dir(temp_dir_length, '\0');
CheckedReadFileExactly(in, &temp_dir[0], temp_dir_length); CheckedReadFileExactly(in, &temp_dir[0], temp_dir_length);
StartHandlerForSelfTestOptions options;
CheckedReadFileExactly(in, &options, sizeof(options));
base::FilePath handler_path = TestPaths::Executable().DirName().Append( base::FilePath handler_path = TestPaths::Executable().DirName().Append(
FILE_PATH_LITERAL("crashpad_handler")); FILE_PATH_LITERAL("crashpad_handler"));
@ -144,27 +169,42 @@ CRASHPAD_CHILD_TEST_MAIN(StartHandlerAtCrashChild) {
test_annotation.Set(kTestAnnotationValue); test_annotation.Set(kTestAnnotationValue);
crashpad::CrashpadClient client; crashpad::CrashpadClient client;
if (!client.StartHandlerAtCrash(handler_path, if (!InstallHandler(&client,
base::FilePath(temp_dir), options.start_handler_at_crash,
base::FilePath(), handler_path,
"", base::FilePath(temp_dir))) {
std::map<std::string, std::string>(),
std::vector<std::string>())) {
return EXIT_FAILURE; 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(); __builtin_trap();
NOTREACHED(); NOTREACHED();
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
class StartHandlerAtCrashTest : public MultiprocessExec { class StartHandlerForSelfInChildTest : public MultiprocessExec {
public: public:
StartHandlerAtCrashTest() : MultiprocessExec() { StartHandlerForSelfInChildTest(const StartHandlerForSelfTestOptions& options)
SetChildTestMainFunction("StartHandlerAtCrashChild"); : MultiprocessExec(), options_(options) {
SetChildTestMainFunction("StartHandlerForSelfTestChild");
if (!options.simulate_crash) {
SetExpectedChildTerminationBuiltinTrap(); SetExpectedChildTerminationBuiltinTrap();
} }
}
private: private:
void MultiprocessParent() override { void MultiprocessParent() override {
@ -174,6 +214,8 @@ class StartHandlerAtCrashTest : public MultiprocessExec {
WritePipeHandle(), &temp_dir_length, sizeof(temp_dir_length))); WritePipeHandle(), &temp_dir_length, sizeof(temp_dir_length)));
ASSERT_TRUE(LoggingWriteFile( ASSERT_TRUE(LoggingWriteFile(
WritePipeHandle(), temp_dir.path().value().data(), temp_dir_length)); WritePipeHandle(), temp_dir.path().value().data(), temp_dir_length));
ASSERT_TRUE(
LoggingWriteFile(WritePipeHandle(), &options_, sizeof(options_)));
// Wait for child to finish. // Wait for child to finish.
CheckedReadFileAtEOF(ReadPipeHandle()); CheckedReadFileAtEOF(ReadPipeHandle());
@ -189,7 +231,11 @@ class StartHandlerAtCrashTest : public MultiprocessExec {
reports.clear(); reports.clear();
ASSERT_EQ(database->GetPendingReports(&reports), ASSERT_EQ(database->GetPendingReports(&reports),
CrashReportDatabase::kNoError); CrashReportDatabase::kNoError);
EXPECT_EQ(reports.size(), 1u); ASSERT_EQ(reports.size(), options_.set_first_chance_handler ? 0u : 1u);
if (options_.set_first_chance_handler) {
return;
}
std::unique_ptr<const CrashReportDatabase::UploadReport> report; std::unique_ptr<const CrashReportDatabase::UploadReport> report;
ASSERT_EQ(database->GetReportForUploading(reports[0].uuid, &report), ASSERT_EQ(database->GetReportForUploading(reports[0].uuid, &report),
@ -197,14 +243,26 @@ class StartHandlerAtCrashTest : public MultiprocessExec {
ValidateDump(report.get()); ValidateDump(report.get());
} }
DISALLOW_COPY_AND_ASSIGN(StartHandlerAtCrashTest); StartHandlerForSelfTestOptions options_;
DISALLOW_COPY_AND_ASSIGN(StartHandlerForSelfInChildTest);
}; };
TEST(CrashpadClient, StartHandlerAtCrash) { TEST_P(StartHandlerForSelfTest, StartHandlerInChild) {
StartHandlerAtCrashTest test; if (Options().set_first_chance_handler && !Options().simulate_crash) {
// TODO(jperaza): test first chance handlers with real crashes.
return;
}
StartHandlerForSelfInChildTest test(Options());
test.Run(); 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. // Test state for starting the handler for another process.
class StartHandlerForClientTest { class StartHandlerForClientTest {
public: public:
@ -213,16 +271,8 @@ class StartHandlerForClientTest {
bool Initialize(bool sanitize) { bool Initialize(bool sanitize) {
sanitize_ = sanitize; sanitize_ = sanitize;
return UnixCredentialSocket::CreateCredentialSocketpair(&client_sock_,
int socks[2]; &server_sock_);
if (socketpair(AF_UNIX, SOCK_STREAM, 0, socks) != 0) {
PLOG(ERROR) << "socketpair";
return false;
}
client_sock_.reset(socks[0]);
server_sock_.reset(socks[1]);
return true;
} }
bool StartHandlerOnDemand() { bool StartHandlerOnDemand() {
@ -298,7 +348,7 @@ class StartHandlerForClientTest {
static void HandleCrash(int signo, siginfo_t* siginfo, void* context) { static void HandleCrash(int signo, siginfo_t* siginfo, void* context) {
auto state = Get(); auto state = Get();
char c; char c = 0;
CHECK(LoggingWriteFile(state->client_sock_, &c, sizeof(c))); CHECK(LoggingWriteFile(state->client_sock_, &c, sizeof(c)));
ExceptionInformation exception_information; ExceptionInformation exception_information;
@ -310,7 +360,7 @@ class StartHandlerForClientTest {
context); context);
exception_information.thread_id = syscall(SYS_gettid); exception_information.thread_id = syscall(SYS_gettid);
ClientInformation info; ExceptionHandlerProtocol::ClientInformation info;
info.exception_information_address = info.exception_information_address =
FromPointerCast<decltype(info.exception_information_address)>( FromPointerCast<decltype(info.exception_information_address)>(
&exception_information); &exception_information);
@ -324,7 +374,7 @@ class StartHandlerForClientTest {
FromPointerCast<VMAddress>(&sanitization_info); FromPointerCast<VMAddress>(&sanitization_info);
} }
ExceptionHandlerClient handler_client(state->client_sock_); ExceptionHandlerClient handler_client(state->client_sock_, false);
CHECK_EQ(handler_client.RequestCrashDump(info), 0); CHECK_EQ(handler_client.RequestCrashDump(info), 0);
Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, nullptr); Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, nullptr);

View File

@ -26,6 +26,7 @@
#include "base/mac/mach_logging.h" #include "base/mac/mach_logging.h"
#include "base/strings/stringprintf.h" #include "base/strings/stringprintf.h"
#include "util/mac/mac_util.h" #include "util/mac/mac_util.h"
#include "util/mach/bootstrap.h"
#include "util/mach/child_port_handshake.h" #include "util/mach/child_port_handshake.h"
#include "util/mach/exception_ports.h" #include "util/mach/exception_ports.h"
#include "util/mach/mach_extensions.h" #include "util/mach/mach_extensions.h"
@ -338,6 +339,7 @@ class HandlerStarter final : public NotifyServer::DefaultInterface {
// this interface. // this interface.
if (!DoubleForkAndExec( if (!DoubleForkAndExec(
argv, argv,
nullptr,
server_write_fd.get(), server_write_fd.get(),
true, true,
restart ? CrashpadClient::UseSystemDefaultHandler : nullptr)) { restart ? CrashpadClient::UseSystemDefaultHandler : nullptr)) {

View File

@ -15,6 +15,7 @@
#include "client/crashpad_client.h" #include "client/crashpad_client.h"
#include <windows.h> #include <windows.h>
#include <signal.h> #include <signal.h>
#include <stdint.h> #include <stdint.h>
#include <string.h> #include <string.h>
@ -35,10 +36,12 @@
#include "util/misc/random_string.h" #include "util/misc/random_string.h"
#include "util/win/address_types.h" #include "util/win/address_types.h"
#include "util/win/command_line.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/critical_section_with_debug_info.h"
#include "util/win/get_function.h" #include "util/win/get_function.h"
#include "util/win/handle.h" #include "util/win/handle.h"
#include "util/win/initial_client_data.h" #include "util/win/initial_client_data.h"
#include "util/win/loader_lock.h"
#include "util/win/nt_internals.h" #include "util/win/nt_internals.h"
#include "util/win/ntstatus_logging.h" #include "util/win/ntstatus_logging.h"
#include "util/win/process_info.h" #include "util/win/process_info.h"
@ -93,9 +96,8 @@ base::subtle::AtomicWord g_handler_startup_state;
CRITICAL_SECTION g_critical_section_with_debug_info; CRITICAL_SECTION g_critical_section_with_debug_info;
void SetHandlerStartupState(StartupState state) { void SetHandlerStartupState(StartupState state) {
DCHECK(state == StartupState::kSucceeded || DCHECK(state == StartupState::kSucceeded || state == StartupState::kFailed);
state == StartupState::kFailed); base::subtle::Release_Store(&g_handler_startup_state,
base::subtle::Acquire_Store(&g_handler_startup_state,
static_cast<base::subtle::AtomicWord>(state)); static_cast<base::subtle::AtomicWord>(state));
} }
@ -103,7 +105,7 @@ StartupState BlockUntilHandlerStartedOrFailed() {
// Wait until we know the handler has either succeeded or failed to start. // Wait until we know the handler has either succeeded or failed to start.
base::subtle::AtomicWord startup_state; base::subtle::AtomicWord startup_state;
while ( while (
(startup_state = base::subtle::Release_Load(&g_handler_startup_state)) == (startup_state = base::subtle::Acquire_Load(&g_handler_startup_state)) ==
static_cast<int>(StartupState::kNotReady)) { static_cast<int>(StartupState::kNotReady)) {
Sleep(1); Sleep(1);
} }
@ -187,11 +189,7 @@ void HandleAbortSignal(int signum) {
EXCEPTION_RECORD record = {}; EXCEPTION_RECORD record = {};
record.ExceptionCode = STATUS_FATAL_APP_EXIT; record.ExceptionCode = STATUS_FATAL_APP_EXIT;
record.ExceptionFlags = EXCEPTION_NONCONTINUABLE; record.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
#if defined(ARCH_CPU_64_BITS) record.ExceptionAddress = ProgramCounterFromCONTEXT(&context);
record.ExceptionAddress = reinterpret_cast<void*>(context.Rip);
#else
record.ExceptionAddress = reinterpret_cast<void*>(context.Eip);
#endif // ARCH_CPU_64_BITS
EXCEPTION_POINTERS exception_pointers; EXCEPTION_POINTERS exception_pointers;
exception_pointers.ContextRecord = &context; exception_pointers.ContextRecord = &context;
@ -349,6 +347,8 @@ class ScopedCallSetHandlerStartupState {
bool StartHandlerProcess( bool StartHandlerProcess(
std::unique_ptr<BackgroundHandlerStartThreadData> data) { std::unique_ptr<BackgroundHandlerStartThreadData> data) {
CHECK(!IsThreadInLoaderLock());
ScopedCallSetHandlerStartupState scoped_startup_state_caller; ScopedCallSetHandlerStartupState scoped_startup_state_caller;
std::wstring command_line; std::wstring command_line;
@ -476,8 +476,25 @@ 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; PROCESS_INFORMATION process_info;
rv = CreateProcess(data->handler.value().c_str(), rv = CreateProcess(
is_embedded_in_dll ? nullptr : data->handler.value().c_str(),
&command_line[0], &command_line[0],
nullptr, nullptr,
nullptr, nullptr,
@ -756,11 +773,7 @@ void CrashpadClient::DumpWithoutCrash(const CONTEXT& context) {
constexpr uint32_t kSimulatedExceptionCode = 0x517a7ed; constexpr uint32_t kSimulatedExceptionCode = 0x517a7ed;
EXCEPTION_RECORD record = {}; EXCEPTION_RECORD record = {};
record.ExceptionCode = kSimulatedExceptionCode; record.ExceptionCode = kSimulatedExceptionCode;
#if defined(ARCH_CPU_64_BITS) record.ExceptionAddress = ProgramCounterFromCONTEXT(&context);
record.ExceptionAddress = reinterpret_cast<void*>(context.Rip);
#else
record.ExceptionAddress = reinterpret_cast<void*>(context.Eip);
#endif // ARCH_CPU_64_BITS
exception_pointers.ExceptionRecord = &record; exception_pointers.ExceptionRecord = &record;

View File

@ -17,7 +17,6 @@
#include <vector> #include <vector>
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "base/macros.h"
#include "base/logging.h" #include "base/logging.h"
#include "gtest/gtest.h" #include "gtest/gtest.h"
#include "test/test_paths.h" #include "test/test_paths.h"

View File

@ -141,15 +141,15 @@ struct CrashpadInfo {
//! //!
//! When handling an exception, the Crashpad handler will scan all modules in //! 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 process. The first one that has a CrashpadInfo structure populated with
//! a value other than #kUnset for this field will dictate whether the handler //! a value other than TriState::kUnset for this field will dictate whether
//! is functional or not. If all modules with a CrashpadInfo structure specify //! the handler is functional or not. If all modules with a CrashpadInfo
//! #kUnset, the handler will be enabled. If disabled, the Crashpad handler //! structure specify TriState::kUnset, the handler will be enabled. If
//! will still run and receive exceptions, but will not take any action on an //! disabled, the Crashpad handler will still run and receive exceptions, but
//! exception on its own behalf, except for the action necessary to determine //! will not take any action on an exception on its own behalf, except for the
//! that it has been disabled. //! action necessary to determine that it has been disabled.
//! //!
//! The Crashpad handler should not normally be disabled. More commonly, it //! The Crashpad handler should not normally be disabled. More commonly, it is
//! is appropriate to disable crash report upload by calling //! appropriate to disable crash report upload by calling
//! Settings::SetUploadsEnabled(). //! Settings::SetUploadsEnabled().
void set_crashpad_handler_behavior(TriState crashpad_handler_behavior) { void set_crashpad_handler_behavior(TriState crashpad_handler_behavior) {
crashpad_handler_behavior_ = 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 //! 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 process. The first one that has a CrashpadInfo structure populated with
//! a value other than #kUnset for this field will dictate whether the //! a value other than TriState::kUnset for this field will dictate whether
//! exception is forwarded to the systems crash reporter. If all modules with //! the exception is forwarded to the systems crash reporter. If all modules
//! a CrashpadInfo structure specify #kUnset, forwarding will be enabled. //! with a CrashpadInfo structure specify TriState::kUnset, forwarding will be
//! Unless disabled, forwarding may still occur if the Crashpad handler is //! enabled. Unless disabled, forwarding may still occur if the Crashpad
//! disabled by SetCrashpadHandlerState(). Even when forwarding is enabled, //! handler is disabled by SetCrashpadHandlerState(). Even when forwarding is
//! the Crashpad handler may choose not to forward all exceptions to the //! enabled, the Crashpad handler may choose not to forward all exceptions to
//! systems crash reporter in cases where it has reason to believe that the //! the systems crash reporter in cases where it has reason to believe that
//! systems crash reporter would not normally have handled the exception in //! the systems crash reporter would not normally have handled the exception
//! Crashpads absence. //! in Crashpads absence.
void set_system_crash_reporter_forwarding( void set_system_crash_reporter_forwarding(
TriState system_crash_reporter_forwarding) { TriState system_crash_reporter_forwarding) {
system_crash_reporter_forwarding_ = 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 //! 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 process. The first one that has a CrashpadInfo structure populated with
//! a value other than #kUnset for this field will dictate whether the extra //! a value other than TriState::kUnset for this field will dictate whether
//! memory is captured. //! the extra memory is captured.
//! //!
//! This causes Crashpad to include pages of data referenced by locals or //! 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 //! 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 //! Note that streams will appear in the minidump in the reverse order to
//! which they are added. //! 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 //! \param[in] stream_type The stream type identifier to use. This should be
//! normally be larger than `MINIDUMP_STREAM_TYPE::LastReservedStream` //! normally be larger than `MINIDUMP_STREAM_TYPE::LastReservedStream`

View File

@ -26,16 +26,13 @@
#define NOTE_ALIGN 4 #define NOTE_ALIGN 4
// This section must be "a"llocated so that it appears in the final binary at // 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 // runtime. The reference to CRASHPAD_INFO_SYMBOL uses an offset relative to
// be performed. // this note to avoid making this note writable, which triggers a bug in GNU
.section .note.crashpad.info,"aw",%note // 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 .balign NOTE_ALIGN
# .globl indicates that it's available to link against other .o files. .hidden CRASHPAD_NOTE:
# indicates that it will not appear in the executable's symbol table.
.globl CRASHPAD_NOTE_REFERENCE
.hidden CRASHPAD_NOTE_REFERENCE
.type CRASHPAD_NOTE_REFERENCE, %object
CRASHPAD_NOTE_REFERENCE:
.long name_end - name // namesz .long name_end - name // namesz
.long desc_end - desc // descsz .long desc_end - desc // descsz
.long CRASHPAD_ELF_NOTE_TYPE_CRASHPAD_INFO // type .long CRASHPAD_ELF_NOTE_TYPE_CRASHPAD_INFO // type
@ -45,15 +42,26 @@ name_end:
.balign NOTE_ALIGN .balign NOTE_ALIGN
desc: desc:
#if defined(__LP64__) #if defined(__LP64__)
.quad CRASHPAD_INFO_SYMBOL .quad CRASHPAD_INFO_SYMBOL - desc
#else #else
#if defined(__LITTLE_ENDIAN__) .long CRASHPAD_INFO_SYMBOL - desc
.long CRASHPAD_INFO_SYMBOL
.long 0
#else
.long 0
.long CRASHPAD_INFO_SYMBOL
#endif // __LITTLE_ENDIAN__
#endif // __LP64__ #endif // __LP64__
desc_end: 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

View File

@ -15,6 +15,7 @@
#include "client/prune_crash_reports.h" #include "client/prune_crash_reports.h"
#include <sys/stat.h> #include <sys/stat.h>
#include <stdint.h>
#include <algorithm> #include <algorithm>
#include <vector> #include <vector>
@ -24,7 +25,7 @@
namespace crashpad { namespace crashpad {
void PruneCrashReportDatabase(CrashReportDatabase* database, size_t PruneCrashReportDatabase(CrashReportDatabase* database,
PruneCondition* condition) { PruneCondition* condition) {
std::vector<CrashReportDatabase::Report> all_reports; std::vector<CrashReportDatabase::Report> all_reports;
CrashReportDatabase::OperationStatus status; CrashReportDatabase::OperationStatus status;
@ -32,14 +33,14 @@ void PruneCrashReportDatabase(CrashReportDatabase* database,
status = database->GetPendingReports(&all_reports); status = database->GetPendingReports(&all_reports);
if (status != CrashReportDatabase::kNoError) { if (status != CrashReportDatabase::kNoError) {
LOG(ERROR) << "PruneCrashReportDatabase: Failed to get pending reports"; LOG(ERROR) << "PruneCrashReportDatabase: Failed to get pending reports";
return; return 0;
} }
std::vector<CrashReportDatabase::Report> completed_reports; std::vector<CrashReportDatabase::Report> completed_reports;
status = database->GetCompletedReports(&completed_reports); status = database->GetCompletedReports(&completed_reports);
if (status != CrashReportDatabase::kNoError) { if (status != CrashReportDatabase::kNoError) {
LOG(ERROR) << "PruneCrashReportDatabase: Failed to get completed reports"; LOG(ERROR) << "PruneCrashReportDatabase: Failed to get completed reports";
return; return 0;
} }
all_reports.insert(all_reports.end(), completed_reports.begin(), all_reports.insert(all_reports.end(), completed_reports.begin(),
completed_reports.end()); completed_reports.end());
@ -50,16 +51,21 @@ void PruneCrashReportDatabase(CrashReportDatabase* database,
return lhs.creation_time > rhs.creation_time; return lhs.creation_time > rhs.creation_time;
}); });
size_t num_pruned = 0;
for (const auto& report : all_reports) { for (const auto& report : all_reports) {
if (condition->ShouldPruneReport(report)) { if (condition->ShouldPruneReport(report)) {
status = database->DeleteReport(report.uuid); status = database->DeleteReport(report.uuid);
if (status != CrashReportDatabase::kNoError) { if (status != CrashReportDatabase::kNoError) {
LOG(ERROR) << "Database Pruning: Failed to remove report " LOG(ERROR) << "Database Pruning: Failed to remove report "
<< report.uuid.ToString(); << report.uuid.ToString();
} else {
num_pruned++;
} }
} }
} }
return num_pruned;
// TODO(rsesek): For databases that do not use a directory structure, it is // TODO(rsesek): For databases that do not use a directory structure, it is
// possible for the metadata sidecar to become corrupted and thus leave // possible for the metadata sidecar to become corrupted and thus leave
// orphaned crash report files on-disk. https://crashpad.chromium.org/bug/66 // orphaned crash report files on-disk. https://crashpad.chromium.org/bug/66
@ -96,19 +102,9 @@ DatabaseSizePruneCondition::~DatabaseSizePruneCondition() {}
bool DatabaseSizePruneCondition::ShouldPruneReport( bool DatabaseSizePruneCondition::ShouldPruneReport(
const CrashReportDatabase::Report& report) { 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. // Round up fractional KB to the next 1-KB boundary.
measured_size_in_kb_ += measured_size_in_kb_ +=
static_cast<size_t>((statbuf.st_size + 1023) / 1024); static_cast<size_t>((report.total_size + 1023) / 1024);
}
return measured_size_in_kb_ > max_size_in_kb_; return measured_size_in_kb_ > max_size_in_kb_;
} }

View File

@ -37,7 +37,9 @@ class PruneCondition;
//! \param[in] database The database from which crash reports will be deleted. //! \param[in] database The database from which crash reports will be deleted.
//! \param[in] condition The condition against which all reports in the database //! \param[in] condition The condition against which all reports in the database
//! will be evaluated. //! will be evaluated.
void PruneCrashReportDatabase(CrashReportDatabase* database, //!
//! \return The number of deleted crash reports.
size_t PruneCrashReportDatabase(CrashReportDatabase* database,
PruneCondition* condition); PruneCondition* condition);
std::unique_ptr<PruneCondition> GetDefaultDatabasePruneCondition(); std::unique_ptr<PruneCondition> GetDefaultDatabasePruneCondition();

View File

@ -15,14 +15,15 @@
#include "client/prune_crash_reports.h" #include "client/prune_crash_reports.h"
#include <stddef.h> #include <stddef.h>
#include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
#include <algorithm> #include <algorithm>
#include <random>
#include <string> #include <string>
#include <vector> #include <vector>
#include "base/numerics/safe_conversions.h" #include "base/numerics/safe_conversions.h"
#include "base/rand_util.h"
#include "gmock/gmock.h" #include "gmock/gmock.h"
#include "gtest/gtest.h" #include "gtest/gtest.h"
#include "test/scoped_temp_dir.h" #include "test/scoped_temp_dir.h"
@ -81,56 +82,49 @@ TEST(PruneCrashReports, AgeCondition) {
} }
TEST(PruneCrashReports, SizeCondition) { TEST(PruneCrashReports, SizeCondition) {
ScopedTempDir temp_dir;
CrashReportDatabase::Report report_1k; 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; 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( DatabaseSizePruneCondition condition(/*max_size_in_kb=*/1);
LoggingOpenFileForWrite(report_1k.file_path, // |report_1k| should not be pruned as the cumulated size is not past 1kB
FileWriteMode::kCreateOrFail, // yet.
FilePermissions::kOwnerOnly));
ASSERT_TRUE(scoped_file_1k.is_valid());
std::string string;
for (int i = 0; i < 128; ++i)
string.push_back(static_cast<char>(i));
for (size_t i = 0; i < 1024; i += string.size()) {
ASSERT_TRUE(LoggingWriteFile(scoped_file_1k.get(),
string.c_str(), string.length()));
}
ScopedFileHandle scoped_file_3k(
LoggingOpenFileForWrite(report_3k.file_path,
FileWriteMode::kCreateOrFail,
FilePermissions::kOwnerOnly));
ASSERT_TRUE(scoped_file_3k.is_valid());
for (size_t i = 0; i < 3072; i += string.size()) {
ASSERT_TRUE(LoggingWriteFile(scoped_file_3k.get(),
string.c_str(), string.length()));
}
}
{
DatabaseSizePruneCondition condition(1);
EXPECT_FALSE(condition.ShouldPruneReport(report_1k)); EXPECT_FALSE(condition.ShouldPruneReport(report_1k));
EXPECT_TRUE(condition.ShouldPruneReport(report_1k)); // |report_3k| should be pruned as the cumulated size is now past 1kB.
}
{
DatabaseSizePruneCondition condition(1);
EXPECT_TRUE(condition.ShouldPruneReport(report_3k)); 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)); 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)); 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)); EXPECT_TRUE(condition.ShouldPruneReport(report_1k));
} }
} }
@ -211,18 +205,16 @@ TEST(PruneCrashReports, PruneOrder) {
using ::testing::Return; using ::testing::Return;
using ::testing::SetArgPointee; using ::testing::SetArgPointee;
const size_t kNumReports = 10;
std::vector<CrashReportDatabase::Report> reports; std::vector<CrashReportDatabase::Report> reports;
for (int i = 0; i < 10; ++i) { for (size_t i = 0; i < kNumReports; ++i) {
CrashReportDatabase::Report temp; CrashReportDatabase::Report temp;
temp.uuid.data_1 = i; temp.uuid.data_1 = static_cast<uint32_t>(i);
temp.creation_time = NDaysAgo(i * 10); temp.creation_time = NDaysAgo(static_cast<int>(i) * 10);
reports.push_back(temp); reports.push_back(temp);
} }
// The randomness from std::rand() is not, so use a better rand() instead. std::mt19937 urng(std::random_device{}());
const auto random_generator = [](ptrdiff_t rand_max) { std::shuffle(reports.begin(), reports.end(), urng);
return base::RandInt(0, base::checked_cast<int>(rand_max) - 1);
};
std::random_shuffle(reports.begin(), reports.end(), random_generator);
std::vector<CrashReportDatabase::Report> pending_reports( std::vector<CrashReportDatabase::Report> pending_reports(
reports.begin(), reports.begin() + 5); reports.begin(), reports.begin() + 5);
std::vector<CrashReportDatabase::Report> completed_reports( std::vector<CrashReportDatabase::Report> completed_reports(
@ -241,7 +233,7 @@ TEST(PruneCrashReports, PruneOrder) {
} }
StaticCondition delete_all(true); StaticCondition delete_all(true);
PruneCrashReportDatabase(&db, &delete_all); EXPECT_EQ(PruneCrashReportDatabase(&db, &delete_all), kNumReports);
} }
} // namespace } // namespace

View File

@ -63,7 +63,8 @@ void Settings::ScopedLockedFileHandle::Destroy() {
CheckedCloseFile(handle_); CheckedCloseFile(handle_);
} }
if (!lockfile_path_.empty()) { 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 #endif // OS_FUCHSIA
struct Settings::Data { struct Settings::Data {
static const uint32_t kSettingsMagic = 'CPds'; static constexpr uint32_t kSettingsMagic = 'CPds';
static const uint32_t kSettingsVersion = 1; static constexpr uint32_t kSettingsVersion = 1;
enum Options : uint32_t { enum Options : uint32_t {
kUploadsEnabled = 1 << 0, kUploadsEnabled = 1 << 0,
@ -225,10 +226,10 @@ Settings::ScopedLockedFileHandle Settings::OpenForReadingAndWriting(
FileHandle handle; FileHandle handle;
if (log_open_error) { if (log_open_error) {
handle = LoggingOpenFileForReadAndWrite( handle = LoggingOpenFileForReadAndWrite(
file_path(), mode, FilePermissions::kWorldReadable); file_path(), mode, FilePermissions::kOwnerOnly);
} else { } else {
handle = OpenFileForReadAndWrite( handle = OpenFileForReadAndWrite(
file_path(), mode, FilePermissions::kWorldReadable); file_path(), mode, FilePermissions::kOwnerOnly);
} }
return MakeScopedLockedFileHandle( return MakeScopedLockedFileHandle(

View File

@ -75,6 +75,10 @@ class Settings {
//! //!
//! The default value is `false`. //! 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. //! \param[out] enabled Whether crash reports should be uploaded.
//! //!
//! \return On success, returns `true`, otherwise returns `false` with an //! \return On success, returns `true`, otherwise returns `false` with an

View File

@ -73,7 +73,7 @@ TEST(SimpleStringDictionary, SimpleStringDictionary) {
EXPECT_FALSE(dict.GetValueForKey("key3")); EXPECT_FALSE(dict.GetValueForKey("key3"));
// Remove by setting value to nullptr // 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 // Now make sure it's not there anymore
EXPECT_FALSE(dict.GetValueForKey("key2")); EXPECT_FALSE(dict.GetValueForKey("key2"));
@ -254,13 +254,14 @@ TEST(SimpleStringDictionary, OutOfSpace) {
TEST(SimpleStringDictionaryDeathTest, SetKeyValueWithNullKey) { TEST(SimpleStringDictionaryDeathTest, SetKeyValueWithNullKey) {
TSimpleStringDictionary<4, 6, 6> map; 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) { TEST(SimpleStringDictionaryDeathTest, GetValueForKeyWithNullKey) {
TSimpleStringDictionary<4, 6, 6> map; TSimpleStringDictionary<4, 6, 6> map;
map.SetKeyValue("hi", "there"); 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")); EXPECT_STREQ("there", map.GetValueForKey("hi"));
} }

View File

@ -20,7 +20,7 @@
#include "base/logging.h" #include "base/logging.h"
#include "base/mac/mach_logging.h" #include "base/mac/mach_logging.h"
#include "base/mac/scoped_mach_port.h" #include "base/mac/scoped_mach_port.h"
#include "base/macros.h" #include "base/stl_util.h"
#include "base/strings/stringprintf.h" #include "base/strings/stringprintf.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "util/mach/exc_client_variants.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()); base::mac::ScopedMachSendRight thread(mach_thread_self());
exception_type_t exception = kMachExceptionSimulated; exception_type_t exception = kMachExceptionSimulated;
mach_exception_data_type_t codes[] = {0, 0}; 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 // 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 // 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; bool success = false;
for (size_t target_type_index = 0; for (size_t target_type_index = 0;
!success && target_type_index < arraysize(kTargetTypes); !success && target_type_index < base::size(kTargetTypes);
++target_type_index) { ++target_type_index) {
ExceptionPorts::ExceptionHandlerVector handlers; ExceptionPorts::ExceptionHandlerVector handlers;
ExceptionPorts exception_ports(kTargetTypes[target_type_index], ExceptionPorts exception_ports(kTargetTypes[target_type_index],

View File

@ -19,6 +19,7 @@
#include <sys/types.h> #include <sys/types.h>
#include "base/macros.h" #include "base/macros.h"
#include "base/stl_util.h"
#include "base/strings/stringprintf.h" #include "base/strings/stringprintf.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "gtest/gtest.h" #include "gtest/gtest.h"
@ -342,15 +343,13 @@ TEST(SimulateCrash, SimulateCrash) {
#endif #endif
}; };
for (size_t target_index = 0; for (size_t target_index = 0; target_index < base::size(kTargets);
target_index < arraysize(kTargets);
++target_index) { ++target_index) {
TestSimulateCrashMac::ExceptionPortsTarget target = kTargets[target_index]; TestSimulateCrashMac::ExceptionPortsTarget target = kTargets[target_index];
SCOPED_TRACE(base::StringPrintf( SCOPED_TRACE(base::StringPrintf(
"target_index %zu, target %d", target_index, target)); "target_index %zu, target %d", target_index, target));
for (size_t behavior_index = 0; for (size_t behavior_index = 0; behavior_index < base::size(kBehaviors);
behavior_index < arraysize(kBehaviors);
++behavior_index) { ++behavior_index) {
exception_behavior_t behavior = kBehaviors[behavior_index]; exception_behavior_t behavior = kBehaviors[behavior_index];
SCOPED_TRACE(base::StringPrintf( SCOPED_TRACE(base::StringPrintf(
@ -364,8 +363,7 @@ TEST(SimulateCrash, SimulateCrash) {
target, behavior, THREAD_STATE_NONE); target, behavior, THREAD_STATE_NONE);
test_simulate_crash_mac.Run(); test_simulate_crash_mac.Run();
} else { } else {
for (size_t flavor_index = 0; for (size_t flavor_index = 0; flavor_index < base::size(kFlavors);
flavor_index < arraysize(kFlavors);
++flavor_index) { ++flavor_index) {
thread_state_flavor_t flavor = kFlavors[flavor_index]; thread_state_flavor_t flavor = kFlavors[flavor_index];
SCOPED_TRACE(base::StringPrintf( SCOPED_TRACE(base::StringPrintf(

View File

@ -13,7 +13,6 @@
# limitations under the License. # limitations under the License.
GERRIT_HOST: True GERRIT_HOST: True
GERRIT_SQUASH_UPLOADS: True
CODE_REVIEW_SERVER: https://chromium-review.googlesource.com/ CODE_REVIEW_SERVER: https://chromium-review.googlesource.com/
VIEW_VC: https://chromium.googlesource.com/crashpad/crashpad/+/ VIEW_VC: https://chromium.googlesource.com/crashpad/crashpad/+/
PROJECT: crashpad PROJECT: crashpad

View File

@ -17,8 +17,14 @@ import("../build/crashpad_buildconfig.gni")
config("compat_config") { config("compat_config") {
include_dirs = [] include_dirs = []
if (crashpad_is_mac) { if (crashpad_is_mac || crashpad_is_ios) {
include_dirs += [ "mac" ] include_dirs += [ "mac" ]
} else {
include_dirs += [ "non_mac" ]
}
if (crashpad_is_ios) {
include_dirs += [ "ios" ]
} }
if (crashpad_is_linux || crashpad_is_android) { if (crashpad_is_linux || crashpad_is_android) {
@ -34,10 +40,14 @@ config("compat_config") {
} else { } else {
include_dirs += [ "non_win" ] include_dirs += [ "non_win" ]
} }
if (!crashpad_is_linux && !crashpad_is_android && !crashpad_is_fuchsia) {
include_dirs += [ "non_elf" ]
}
} }
template("compat_target") { template("compat_target") {
if (crashpad_is_mac) { if (crashpad_is_mac || crashpad_is_ios) {
# There are no sources to compile, which doesnt mix will with a # There are no sources to compile, which doesnt mix will with a
# static_library. # static_library.
group(target_name) { group(target_name) {
@ -53,21 +63,39 @@ template("compat_target") {
compat_target("compat") { compat_target("compat") {
sources = [] sources = []
if (crashpad_is_mac) { if (crashpad_is_mac || crashpad_is_ios) {
sources += [ sources += [
"mac/AvailabilityMacros.h", "mac/AvailabilityMacros.h",
"mac/kern/exc_resource.h", "mac/kern/exc_resource.h",
"mac/mach-o/loader.h", "mac/mach-o/loader.h",
"mac/mach/i386/thread_state.h",
"mac/mach/mach.h", "mac/mach/mach.h",
"mac/sys/resource.h", "mac/sys/resource.h",
] ]
} else { } 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) { if (crashpad_is_linux || crashpad_is_android) {
sources += [ sources += [
"linux/signal.h", "linux/signal.h",
"linux/sys/mman.cc",
"linux/sys/mman.h",
"linux/sys/ptrace.h", "linux/sys/ptrace.h",
"linux/sys/user.h", "linux/sys/user.h",
] ]
@ -75,6 +103,8 @@ compat_target("compat") {
if (crashpad_is_android) { if (crashpad_is_android) {
sources += [ sources += [
"android/android/api-level.cc",
"android/android/api-level.h",
"android/dlfcn_internal.cc", "android/dlfcn_internal.cc",
"android/dlfcn_internal.h", "android/dlfcn_internal.h",
"android/elf.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 = [ public_configs = [
":compat_config", ":compat_config",
"..:crashpad_config", "..:crashpad_config",
@ -121,7 +155,15 @@ compat_target("compat") {
deps = [] deps = []
if (!crashpad_is_mac) {
deps += [ "../third_party/xnu" ]
}
if (crashpad_is_win) { if (crashpad_is_win) {
deps += [ "../third_party/getopt" ] deps += [ "../third_party/getopt" ]
} }
if (!crashpad_is_linux && !crashpad_is_android && !crashpad_is_fuchsia) {
deps += [ "../third_party/glibc" ]
}
} }

View File

@ -0,0 +1,50 @@
// Copyright 2018 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <android/api-level.h>
#include <dlfcn.h>
#include <limits.h>
#include <stdlib.h>
#include <sys/system_properties.h>
#include "dlfcn_internal.h"
#if __NDK_MAJOR__ < 20
extern "C" {
int android_get_device_api_level() {
using FuncType = int (*)();
static const FuncType bionic_get_device_api_level =
reinterpret_cast<FuncType>(
crashpad::internal::Dlsym(RTLD_NEXT, "android_get_device_api_level"));
if (bionic_get_device_api_level) {
return bionic_get_device_api_level();
}
char api_string[PROP_VALUE_MAX];
int length = __system_property_get("ro.build.version.sdk", api_string);
if (length <= 0) {
return -1;
}
int api_level = atoi(api_string);
return api_level > 0 ? api_level : -1;
}
} // extern "C"
#endif // __NDK_MAJOR__ < 20

View File

@ -0,0 +1,39 @@
// Copyright 2018 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef CRASHPAD_COMPAT_ANDROID_ANDROID_API_LEVEL_H_
#define CRASHPAD_COMPAT_ANDROID_ANDROID_API_LEVEL_H_
#include_next <android/api-level.h>
#include <android/ndk-version.h>
#include <sys/cdefs.h>
#if __NDK_MAJOR__ < 20
#ifdef __cplusplus
extern "C" {
#endif
// Returns the API level of the device or -1 if it can't be determined. This
// function is provided by NDK r20.
int android_get_device_api_level();
#ifdef __cplusplus
} // extern "C"
#endif
#endif // __NDK_MAJOR__ < 20
#endif // CRASHPAD_COMPAT_ANDROID_ANDROID_API_LEVEL_H_

View File

@ -35,7 +35,7 @@ extern "C" void* __mmap2(void* addr,
namespace { namespace {
template <typename T> template <typename T>
T Align(T value, uint8_t alignment) { T Align(T value, size_t alignment) {
return (value + alignment - 1) & ~(alignment - 1); return (value + alignment - 1) & ~(alignment - 1);
} }

View File

@ -20,6 +20,8 @@
{ {
'target_name': 'crashpad_compat', 'target_name': 'crashpad_compat',
'sources': [ 'sources': [
'android/android/api-level.cc',
'android/android/api-level.h',
'android/dlfcn_internal.cc', 'android/dlfcn_internal.cc',
'android/dlfcn_internal.h', 'android/dlfcn_internal.h',
'android/elf.h', 'android/elf.h',

20
compat/ios/mach/exc.defs Normal file
View File

@ -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_

View File

@ -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_

View File

@ -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_

View File

@ -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"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with 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 // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#ifndef CRASHPAD_UTIL_MISC_ARRAYSIZE_UNSAFE_H_ #ifndef CRASHPAD_COMPAT_IOS_MACH_MACHINE_MACHINE_TYPES_DEFS_
#define CRASHPAD_UTIL_MISC_ARRAYSIZE_UNSAFE_H_ #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 arrays size… #endif // CRASHPAD_COMPAT_IOS_MACH_MACHINE_MACHINE_TYPES_DEFS_
//!
//! `#%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_

View File

@ -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_

35
compat/linux/sys/mman.cc Normal file
View File

@ -0,0 +1,35 @@
// Copyright 2019 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <sys/mman.h>
#include <dlfcn.h>
#include <sys/syscall.h>
#include <unistd.h>
#if defined(__GLIBC__)
extern "C" {
int memfd_create(const char* name, unsigned int flags) {
using MemfdCreateType = int (*)(const char*, int);
static const MemfdCreateType next_memfd_create =
reinterpret_cast<MemfdCreateType>(dlsym(RTLD_NEXT, "memfd_create"));
return next_memfd_create ? next_memfd_create(name, flags)
: syscall(SYS_memfd_create, name, flags);
}
} // extern "C"
#endif // __GLIBC__

40
compat/linux/sys/mman.h Normal file
View File

@ -0,0 +1,40 @@
// Copyright 2019 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef CRASHPAD_COMPAT_LINUX_SYS_MMAN_H_
#define CRASHPAD_COMPAT_LINUX_SYS_MMAN_H_
#include_next <sys/mman.h>
#include <features.h>
// There's no memfd_create() wrapper before glibc 2.27.
// This can't select for glibc < 2.27 because linux-chromeos-rel bots build this
// code using a sysroot which has glibc 2.27, but then run it on Ubuntu 16.04,
// which doesn't.
#if defined(__GLIBC__)
#ifdef __cplusplus
extern "C" {
#endif
int memfd_create(const char* name, unsigned int flags);
#ifdef __cplusplus
} // extern "C"
#endif
#endif // __GLIBC__
#endif // CRASHPAD_COMPAT_LINUX_SYS_MMAN_H_

View File

@ -26,7 +26,8 @@
static constexpr __ptrace_request PTRACE_GET_THREAD_AREA = static constexpr __ptrace_request PTRACE_GET_THREAD_AREA =
static_cast<__ptrace_request>(25); static_cast<__ptrace_request>(25);
#define PTRACE_GET_THREAD_AREA PTRACE_GET_THREAD_AREA #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 constexpr __ptrace_request PTRACE_GET_THREAD_AREA =
static_cast<__ptrace_request>(22); static_cast<__ptrace_request>(22);
#define PTRACE_GET_THREAD_AREA PTRACE_GET_THREAD_AREA #define PTRACE_GET_THREAD_AREA PTRACE_GET_THREAD_AREA

View File

@ -59,4 +59,16 @@
#define MAC_OS_X_VERSION_10_13 101300 #define MAC_OS_X_VERSION_10_13 101300
#endif #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_ #endif // CRASHPAD_COMPAT_MAC_AVAILABILITYMACROS_H_

20
compat/non_elf/elf.h Normal file
View File

@ -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_

View File

@ -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_

View File

@ -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_

View File

@ -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_

View File

@ -214,7 +214,7 @@ union __attribute__((packed, aligned(4))) CPU_INFORMATION {
//! `cpuid 0x80000001` `edx`. //! `cpuid 0x80000001` `edx`.
//! //!
//! This field is only valid if #VendorId identifies the CPU vendor as //! This field is only valid if #VendorId identifies the CPU vendor as
//! “AuthenticAMD”. //! “AuthenticAMD” or "HygonGenuine".
uint32_t AMDExtendedCpuFeatures; uint32_t AMDExtendedCpuFeatures;
} X86CpuInfo; } X86CpuInfo;

View File

@ -20,6 +20,4 @@
#include <stdint.h> #include <stdint.h>
typedef unsigned int pid_t;
#endif // CRASHPAD_COMPAT_WIN_SYS_TYPES_H_ #endif // CRASHPAD_COMPAT_WIN_SYS_TYPES_H_

View File

@ -106,68 +106,67 @@ GN and Ninja are part of the
[depot_tools](https://www.chromium.org/developers/how-tos/depottools). Theres [depot_tools](https://www.chromium.org/developers/how-tos/depottools). Theres
no need to install them separately. 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 ### Android
Crashpads Android port is in its early stages. This build relies on This build relies on cross-compilation. Its possible to develop Crashpad for
cross-compilation. Its possible to develop Crashpad for Android on any platform Android on any platform that the [Android NDK (Native Development Kit)]
that the [Android NDK (Native Development (https://developer.android.com/ndk/) runs on.
Kit)](https://developer.android.com/ndk/) runs on.
If its not already present on your system, [download the NDK package for your If its not already present on your system, [download the NDK package for your
system](https://developer.android.com/ndk/downloads/) and expand it to a system](https://developer.android.com/ndk/downloads/) and expand it to a
suitable location. These instructions assume that its been expanded to suitable location. These instructions assume that its been expanded to
`~/android-ndk-r16`. `~/android-ndk-r20`.
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
```
Note that Chrome uses Android API level 21 for 64-bit platforms and 16 for Note that Chrome uses Android API level 21 for 64-bit platforms and 16 for
32-bit platforms. See Chromes 32-bit platforms. See Chromes
[`build/config/android/config.gni`](https://chromium.googlesource.com/chromium/src/+/master/build/config/android/config.gni) [`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`. which sets `_android_api_level` and `_android64_api_level`.
To configure a Crashpad build for Android using the standalone toolchain To configure a Crashpad build for Android use `gyp_crashpad_android.py`. This
assembled above, use `gyp_crashpad_android.py`. This script is a wrapper for script is a wrapper for `gyp_crashpad.py` that sets several environment
`gyp_crashpad.py` that sets several environment variables directing the build to variables directing the build to the toolchain, and several GYP options to
the standalone toolchain, and several GYP options to identify an Android build. identify an Android build. This must be done after any `gclient sync`, or
This must be done after any `gclient sync`, or instead of any `gclient runhooks` instead of any `gclient runhooks` operation.
operation.
``` ```
$ cd ~/crashpad/crashpad $ cd ~/crashpad/crashpad
$ python build/gyp_crashpad_android.py \ python build/gyp_crashpad_android.py \
--ndk ~/android-ndk-r16_arm64_api21 \ --ndk ~/usr/lib/android-ndk-r20 --arch arm64 --api-level 21 \
--generator-output out/android_arm64_api21 --generator-output=out/android_arm64_api21 \
``` ```
`gyp_crashpad_android.py` detects the build type based on the characteristics of To build, direct `ninja` to the specific `out` directory chosen by the
the standalone toolchain given in its `--ndk` argument.
`gyp_crashpad_android.py` sets the build up to use Clang by default. Its 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
`--generator-output` argument to `gyp_crashpad_android.py`. `--generator-output` argument to `gyp_crashpad_android.py`.
``` ```
$ ninja -C out/android_arm64_api21/out/Debug \ $ ninja -C out/android_arm64_api21/out/Debug all
crashpad_test_test crashpad_util_test
``` ```
## Testing ## Testing
@ -193,6 +192,13 @@ $ cd ~/crashpad/crashpad
$ python build/run_tests.py out/Debug $ 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 ### Windows
On Windows, `end_to_end_test.py` requires the CDB debugger, installed with 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 location on the detected or selected device, run them, and clean up after itself
when done. 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 ## Contributing
Crashpads contribution process is very similar to [Chromiums contribution Crashpads contribution process is very similar to [Chromiums contribution
@ -303,7 +328,7 @@ file.
## Buildbot ## Buildbot
The [Crashpad Buildbot](https://build.chromium.org/p/client.crashpad/) performs The [Crashpad Buildbot](https://ci.chromium.org/p/crashpad/g/main/console)
automated builds and tests of Crashpad. Before checking out or updating the performs automated builds and tests of Crashpad. Before checking out or updating
Crashpad source code, and after checking in a new change, it is prudent to check the Crashpad source code, and after checking in a new change, it is prudent to
the Buildbot to ensure that “the tree is green.” check the Buildbot to ensure that “the tree is green.”

View File

@ -240,7 +240,24 @@ of command line arguments in this mode.
#### Linux/Android #### 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 ### Capturing Exceptions
@ -290,8 +307,16 @@ here.](https://crashpad.chromium.org/bug/133)
#### Linux/Android #### Linux/Android
TODO(mmentovai): describe this. See [this preliminary On Linux, exceptions are dispatched as signals to the crashing thread. Crashpad
doc.](https://goto.google.com/crashpad-android-dd) 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 ### The CrashpadInfo structure

View File

@ -18,24 +18,27 @@ limitations under the License.
## Completed ## Completed
Crashpad currently consists of a crash-reporting client and some related tools Crashpad has complete crash-reporting clients and some related tools for macOS,
for macOS and Windows. The core client work for both platforms is substantially Windows, Fuchsia, and Linux (including Android and Chromium OS). Crashpad became
complete. Crashpad became the crash reporter client for the crash reporter client for [Chromium](https://www.chromium.org/Home) on macOS
[Chromium](https://www.chromium.org/Home) on macOS as of [March as of [March
2015](https://chromium.googlesource.com/chromium/src/\+/d413b2dcb54d523811d386f1ff4084f677a6d089), 2015](https://chromium.googlesource.com/chromium/src/\+/d413b2dcb54d523811d386f1ff4084f677a6d089),
and on Windows as of [November Windows as of [November
2015](https://chromium.googlesource.com/chromium/src/\+/cfa5b01bb1d06bf96967bd37e21a44752801948c). 2015](https://chromium.googlesource.com/chromium/src/\+/cfa5b01bb1d06bf96967bd37e21a44752801948c),
and Android as of [January
2019](https://chromium.googlesource.com/chromium/src/+/f890e4b5495ab693d2d37aec3c378239946154f7).
## In Progress ## In Progress
Initial work on a Crashpad client for Chromium is transitioning to Crashpad for [Chromium OS and Desktop
[Android](https://crashpad.chromium.org/bug/30) has begun. This is currently in Linux](https://crbug.com/942279).
the early implementation phase.
Work has begun on a Crashpad client for
[iOS](https://crashpad.chromium.org/bug/31).
## Future ## Future
There are plans to bring Crashpad clients to other operating systems in the There are also plans to implement a [crash report
future, including a more generic non-Android Linux implementation. There are
also plans to implement a [crash report
processor](https://crashpad.chromium.org/bug/29) as part of Crashpad. No processor](https://crashpad.chromium.org/bug/29) as part of Crashpad. No
timeline for completing this work has been set yet. timeline for completing this work has been set yet.

View File

@ -1,4 +1,4 @@
# Doxyfile 1.8.14 # Doxyfile 1.8.18
# This file describes the settings to be used by the documentation system # This file describes the settings to be used by the documentation system
# doxygen (www.doxygen.org) for a project. # doxygen (www.doxygen.org) for a project.
@ -17,10 +17,10 @@
# Project related configuration options # Project related configuration options
#--------------------------------------------------------------------------- #---------------------------------------------------------------------------
# This tag specifies the encoding used for all characters in the config file # This tag specifies the encoding used for all characters in the configuration
# that follow. The default is UTF-8 which is also the encoding used for all text # file that follow. The default is UTF-8 which is also the encoding used for all
# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv # text before the first occurrence of this tag. Doxygen uses libiconv (or the
# built into libc) for the transcoding. See # iconv built into libc) for the transcoding. See
# https://www.gnu.org/software/libiconv/ for the list of possible encodings. # https://www.gnu.org/software/libiconv/ for the list of possible encodings.
# The default value is: UTF-8. # The default value is: UTF-8.
@ -93,6 +93,14 @@ ALLOW_UNICODE_NAMES = NO
OUTPUT_LANGUAGE = English 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 # 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 # descriptions after the members that are listed in the file and class
# documentation (similar to Javadoc). Set to NO to disable this. # documentation (similar to Javadoc). Set to NO to disable this.
@ -181,6 +189,16 @@ SHORT_NAMES = NO
JAVADOC_AUTOBRIEF = 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 # 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 # 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 # 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 # "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 # 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. # 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 = 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 # 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 # 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 # 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 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 # 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 # 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 # 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 # 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, # language is one of the parsers supported by doxygen: IDL, Java, JavaScript,
# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: # Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL,
# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: # Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:
# Fortran. In the later case the parser tries to guess whether the code is fixed # FortranFree, unknown formatted Fortran: Fortran. In the later case the parser
# or free formatted code, this is the default for Fortran type files), VHDL. For # tries to guess whether the code is fixed or free formatted code, this is the
# instance to make doxygen treat .inc files as Fortran files (default is PHP), # default for Fortran type files). For instance to make doxygen treat .inc files
# and .f files as C (default is Fortran), use: inc=Fortran f=C. # 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. # 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 # If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
# according to the Markdown format, which allows for more readable # 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 # 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 # mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
# case of backward compatibilities issues. # 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 # to that level are automatically included in the table of contents, even if
# they do not have an id attribute. # they do not have an id attribute.
# Note: This feature currently applies only to Markdown headings. # 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. # This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
TOC_INCLUDE_HEADINGS = 0 TOC_INCLUDE_HEADINGS = 0
@ -436,6 +461,12 @@ EXTRACT_ALL = NO
EXTRACT_PRIVATE = 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 # If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal
# scope will be included in the documentation. # scope will be included in the documentation.
# The default value is: NO. # The default value is: NO.
@ -490,8 +521,8 @@ HIDE_UNDOC_MEMBERS = NO
HIDE_UNDOC_CLASSES = NO HIDE_UNDOC_CLASSES = NO
# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend # 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 # declarations. If set to NO, these declarations will be included in the
# included in the documentation. # documentation.
# The default value is: NO. # The default value is: NO.
HIDE_FRIEND_COMPOUNDS = 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 # 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 # 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 # 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. # The default value is: system dependent.
CASE_SENSE_NAMES = YES 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 # 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 # are documented, but have no documentation for their parameters or return
# value. If set to NO, doxygen will only warn about wrong or incomplete # 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. # The default value is: NO.
WARN_NO_PARAMDOC = 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, # If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, # *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, # *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, # *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment),
# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf. # *.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 \ FILE_PATTERNS = *.c \
*.cc \ *.cc \
@ -968,7 +1002,7 @@ INLINE_SOURCES = NO
STRIP_CODE_COMMENTS = YES STRIP_CODE_COMMENTS = YES
# If the REFERENCED_BY_RELATION tag is set to YES then for each documented # 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. # The default value is: NO.
REFERENCED_BY_RELATION = NO REFERENCED_BY_RELATION = NO
@ -1005,7 +1039,7 @@ SOURCE_TOOLTIPS = YES
# #
# To use it do the following: # To use it do the following:
# - Install the latest version of global # - 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 # - Make sure the INPUT points to the root of the source tree
# - Run doxygen as normal # - 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 # 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 # 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 # 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. # like the Qt help browser.
# The default value is: YES. # The default value is: YES.
# This tag requires that the tag GENERATE_HTML is set to 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 # 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 # generated that can be used as input for Apple's Xcode 3 integrated development
# environment (see: https://developer.apple.com/tools/xcode/), introduced with # environment (see: https://developer.apple.com/xcode/), introduced with OSX
# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a # 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 # Makefile in the HTML output directory. Running make will produce the docset in
# that directory and running make install will install 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 # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
# startup. See https://developer.apple.com/tools/creatingdocsetswithdoxygen.html # startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy
# for more information. # genXcode/_index.html for more information.
# The default value is: NO. # The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES. # 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 # 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 # 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 # 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. # Windows.
# #
# The HTML Help Workshop contains a compiler that can convert all HTML output # 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 # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
# Project output. For more information please see Qt Help Project / Namespace # 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. # The default value is: org.doxygen.Project.
# This tag requires that the tag GENERATE_QHP is set to YES. # 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 # 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 # 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. # The default value is: doc.
# This tag requires that the tag GENERATE_QHP is set to YES. # 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 # 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 # 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. # This tag requires that the tag GENERATE_QHP is set to YES.
QHP_CUST_FILTER_NAME = QHP_CUST_FILTER_NAME =
# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the # 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 # 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. # This tag requires that the tag GENERATE_QHP is set to YES.
QHP_CUST_FILTER_ATTRS = QHP_CUST_FILTER_ATTRS =
# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
# project's filter section matches. Qt Help Project / Filter Attributes (see: # 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. # This tag requires that the tag GENERATE_QHP is set to YES.
QHP_SECT_FILTER_ATTRS = QHP_SECT_FILTER_ATTRS =
@ -1450,6 +1487,17 @@ TREEVIEW_WIDTH = 250
EXT_LINKS_IN_WINDOW = NO 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 # 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 # 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 # doxygen run you need to manually remove any form_*.png images from the HTML
@ -1470,8 +1518,14 @@ FORMULA_FONTSIZE = 10
FORMULA_TRANSPARENT = YES 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 # 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 # 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 # 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 # 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 # 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. However, it is strongly recommended to install a local copy of
# MathJax from https://www.mathjax.org before deployment. # 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. # This tag requires that the tag USE_MATHJAX is set to YES.
MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest
@ -1541,7 +1595,7 @@ MATHJAX_CODEFILE =
SEARCHENGINE = YES SEARCHENGINE = YES
# When the SERVER_BASED_SEARCH tag is enabled the search engine will be # 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 # 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 # 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 # 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 # The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
# invoked. # invoked.
# #
# Note that when enabling USE_PDFLATEX this option is only used for generating # Note that when not enabling USE_PDFLATEX the default is latex when enabling
# bitmaps for formulas in the HTML output, but not in the Makefile that is # USE_PDFLATEX the default is pdflatex and when in the later case latex is
# written to the output directory. # chosen this is overwritten by pdflatex. For specific output languages the
# The default file is: latex. # 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. # This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_CMD_NAME = latex LATEX_CMD_NAME = latex
# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate # The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
# index for LaTeX. # 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. # The default file is: makeindex.
# This tag requires that the tag GENERATE_LATEX is set to YES. # This tag requires that the tag GENERATE_LATEX is set to YES.
MAKEINDEX_CMD_NAME = makeindex 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 # 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 # documents. This may be useful for small projects and may help to save some
# trees in general. # trees in general.
@ -1774,6 +1842,14 @@ LATEX_BIB_STYLE = plain
LATEX_TIMESTAMP = NO 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 # Configuration options related to the RTF output
#--------------------------------------------------------------------------- #---------------------------------------------------------------------------
@ -1813,9 +1889,9 @@ COMPACT_RTF = NO
RTF_HYPERLINKS = NO RTF_HYPERLINKS = NO
# Load stylesheet definitions from file. Syntax is similar to doxygen's config # Load stylesheet definitions from file. Syntax is similar to doxygen's
# file, i.e. a series of assignments. You only have to provide replacements, # configuration file, i.e. a series of assignments. You only have to provide
# missing definitions are set to their default value. # replacements, missing definitions are set to their default value.
# #
# See also section "Doxygen usage" for information on how to generate the # See also section "Doxygen usage" for information on how to generate the
# default style sheet that doxygen normally uses. # default style sheet that doxygen normally uses.
@ -1824,8 +1900,8 @@ RTF_HYPERLINKS = NO
RTF_STYLESHEET_FILE = RTF_STYLESHEET_FILE =
# Set optional variables used in the generation of an RTF document. Syntax is # 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 # similar to doxygen's configuration file. A template extensions file can be
# using doxygen -e rtf extensionFile. # generated using doxygen -e rtf extensionFile.
# This tag requires that the tag GENERATE_RTF is set to YES. # This tag requires that the tag GENERATE_RTF is set to YES.
RTF_EXTENSIONS_FILE = RTF_EXTENSIONS_FILE =
@ -1911,6 +1987,13 @@ XML_OUTPUT = xml
XML_PROGRAMLISTING = YES 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 # Configuration options related to the DOCBOOK output
#--------------------------------------------------------------------------- #---------------------------------------------------------------------------
@ -2116,12 +2199,6 @@ EXTERNAL_GROUPS = YES
EXTERNAL_PAGES = 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 # Configuration options related to the dot tool
#--------------------------------------------------------------------------- #---------------------------------------------------------------------------
@ -2135,15 +2212,6 @@ PERL_PATH = /usr/bin/perl
CLASS_DIAGRAMS = YES 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 # 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 # 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. # DIA_PATH tag allows you to specify the directory where the dia binary resides.

View File

@ -1,5 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python
# coding: utf-8
# Copyright 2017 The Crashpad Authors. All rights reserved. # Copyright 2017 The Crashpad Authors. All rights reserved.
# #

View File

@ -42,6 +42,8 @@ static_library("handler") {
if (crashpad_is_linux || crashpad_is_android) { if (crashpad_is_linux || crashpad_is_android) {
set_sources_assignment_filter([]) set_sources_assignment_filter([])
sources += [ sources += [
"linux/capture_snapshot.cc",
"linux/capture_snapshot.h",
"linux/crash_report_exception_handler.cc", "linux/crash_report_exception_handler.cc",
"linux/crash_report_exception_handler.h", "linux/crash_report_exception_handler.h",
"linux/exception_handler_server.cc", "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) { if (crashpad_is_win) {
sources += [ sources += [
"win/crash_report_exception_handler.cc", "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" ] public_configs = [ "..:crashpad_config" ]
deps = [ public_deps = [
"../client", "../client",
"../compat", "../third_party/mini_chromium:base",
"../util",
]
deps = [
"../minidump", "../minidump",
"../snapshot", "../snapshot",
"../third_party/mini_chromium:base",
"../tools:tool_support", "../tools:tool_support",
"../util",
] ]
if (crashpad_is_win) { 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") { source_set("handler_test") {
testonly = true testonly = true
sources = [ sources = [ "minidump_to_upload_parameters_test.cc" ]
"minidump_to_upload_parameters_test.cc",
]
if (crashpad_is_linux || crashpad_is_android) { if (crashpad_is_linux || crashpad_is_android) {
sources += [ "linux/exception_handler_server_test.cc" ] sources += [ "linux/exception_handler_server_test.cc" ]
@ -110,6 +120,8 @@ source_set("handler_test") {
] ]
if (crashpad_is_win) { if (crashpad_is_win) {
deps += [ "../minidump:test_support" ]
data_deps = [ data_deps = [
":crashpad_handler_test_extended_handler", ":crashpad_handler_test_extended_handler",
":fake_handler_that_crashes_at_startup", ":fake_handler_that_crashes_at_startup",
@ -117,38 +129,20 @@ source_set("handler_test") {
} }
} }
crashpad_executable("crashpad_handler") { if (!crashpad_is_ios) {
sources = [ crashpad_executable("crashpad_handler") {
"main.cc", sources = [ "main.cc" ]
]
deps = [ deps = [
":handler", ":handler",
"../build:default_exe_manifest_win", "../build:default_exe_manifest_win",
"../compat", "../compat",
"../third_party/mini_chromium:base", "../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_win) {
if (crashpad_is_in_chromium) { if (crashpad_is_in_chromium || crashpad_is_in_dart) {
remove_configs = [ "//build/config/win:console" ] remove_configs = [ "//build/config/win:console" ]
configs = [ "//build/config/win:windowed" ] configs = [ "//build/config/win:windowed" ]
} else { } else {
@ -158,6 +152,7 @@ crashpad_executable("crashpad_handler") {
[ "//third_party/mini_chromium/mini_chromium/build:win_windowed" ] [ "//third_party/mini_chromium/mini_chromium/build:win_windowed" ]
} }
} }
}
} }
# There is not any normal way to package native executables in an Android APK. # There is not any normal way to package native executables in an Android APK.
@ -166,26 +161,32 @@ crashpad_executable("crashpad_handler") {
# handler executable an acceptable name. # handler executable an acceptable name.
if (crashpad_is_android) { if (crashpad_is_android) {
copy("crashpad_handler_named_as_so") { copy("crashpad_handler_named_as_so") {
deps = [ deps = [ ":crashpad_handler" ]
":crashpad_handler",
]
sources = [ sources = [ "$root_out_dir/crashpad_handler" ]
"$root_out_dir/crashpad_handler",
]
outputs = [ outputs = [ "$root_out_dir/libcrashpad_handler.so" ]
"$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
}
} }
} }
crashpad_executable("crashpad_handler_test_extended_handler") { if (!crashpad_is_ios) {
crashpad_executable("crashpad_handler_test_extended_handler") {
testonly = true testonly = true
sources = [ sources = [ "crashpad_handler_test_extended_handler.cc" ]
"crashpad_handler_test_extended_handler.cc",
]
deps = [ deps = [
":handler", ":handler",
@ -195,13 +196,12 @@ crashpad_executable("crashpad_handler_test_extended_handler") {
"../third_party/mini_chromium:base", "../third_party/mini_chromium:base",
"../tools:tool_support", "../tools:tool_support",
] ]
}
} }
if (crashpad_is_win) { if (crashpad_is_win) {
crashpad_executable("crashpad_handler_com") { crashpad_executable("crashpad_handler_com") {
sources = [ sources = [ "main.cc" ]
"main.cc",
]
# Avoid .exp, .ilk, and .lib file collisions with crashpad_handler.exe by # Avoid .exp, .ilk, and .lib file collisions with crashpad_handler.exe by
# having this target produce crashpad_handler_com.com. Dont use this target # having this target produce crashpad_handler_com.com. Dont use this target
@ -213,27 +213,20 @@ if (crashpad_is_win) {
"../build:default_exe_manifest_win", "../build:default_exe_manifest_win",
"../compat", "../compat",
"../third_party/mini_chromium:base", "../third_party/mini_chromium:base",
"../tools:tool_support",
] ]
} }
copy("crashpad_handler_console") { copy("crashpad_handler_console") {
deps = [ deps = [ ":crashpad_handler_com" ]
":crashpad_handler_com", sources = [ "$root_out_dir/crashpad_handler_com.com" ]
] outputs = [ "$root_out_dir/crashpad_handler.com" ]
sources = [
"$root_out_dir/crashpad_handler_com.com",
]
outputs = [
"$root_out_dir/crashpad_handler.com",
]
} }
crashpad_executable("crash_other_program") { crashpad_executable("crash_other_program") {
testonly = true testonly = true
sources = [ sources = [ "win/crash_other_program.cc" ]
"win/crash_other_program.cc",
]
deps = [ deps = [
"../client", "../client",
@ -246,9 +239,7 @@ if (crashpad_is_win) {
crashpad_executable("crashy_program") { crashpad_executable("crashy_program") {
testonly = true testonly = true
sources = [ sources = [ "win/crashy_test_program.cc" ]
"win/crashy_test_program.cc",
]
deps = [ deps = [
"../client", "../client",
@ -259,9 +250,7 @@ if (crashpad_is_win) {
crashpad_executable("crashy_signal") { crashpad_executable("crashy_signal") {
testonly = true testonly = true
sources = [ sources = [ "win/crashy_signal.cc" ]
"win/crashy_signal.cc",
]
cflags = [ "/wd4702" ] # Unreachable code. cflags = [ "/wd4702" ] # Unreachable code.
@ -274,17 +263,13 @@ if (crashpad_is_win) {
crashpad_executable("fake_handler_that_crashes_at_startup") { crashpad_executable("fake_handler_that_crashes_at_startup") {
testonly = true testonly = true
sources = [ sources = [ "win/fake_handler_that_crashes_at_startup.cc" ]
"win/fake_handler_that_crashes_at_startup.cc",
]
} }
crashpad_executable("hanging_program") { crashpad_executable("hanging_program") {
testonly = true testonly = true
sources = [ sources = [ "win/hanging_program.cc" ]
"win/hanging_program.cc",
]
deps = [ deps = [
"../client", "../client",
@ -295,17 +280,13 @@ if (crashpad_is_win) {
crashpad_loadable_module("loader_lock_dll") { crashpad_loadable_module("loader_lock_dll") {
testonly = true testonly = true
sources = [ sources = [ "win/loader_lock_dll.cc" ]
"win/loader_lock_dll.cc",
]
} }
crashpad_executable("self_destroying_program") { crashpad_executable("self_destroying_program") {
testonly = true testonly = true
sources = [ sources = [ "win/self_destroying_test_program.cc" ]
"win/self_destroying_test_program.cc",
]
deps = [ deps = [
"../client", "../client",
@ -320,9 +301,7 @@ if (crashpad_is_win) {
crashpad_executable("crashy_z7_loader") { crashpad_executable("crashy_z7_loader") {
testonly = true testonly = true
sources = [ sources = [ "win/crashy_test_z7_loader.cc" ]
"win/crashy_test_z7_loader.cc",
]
deps = [ deps = [
"../client", "../client",

View File

@ -246,12 +246,6 @@ void CrashReportUploadThread::ProcessPendingReport(
CrashReportUploadThread::UploadResult CrashReportUploadThread::UploadReport( CrashReportUploadThread::UploadResult CrashReportUploadThread::UploadReport(
const CrashReportDatabase::UploadReport* report, const CrashReportDatabase::UploadReport* report,
std::string* response_body) { std::string* response_body) {
#if defined(OS_ANDROID)
// TODO(jperaza): This method can be enabled on Android after HTTPTransport is
// implemented and Crashpad takes over upload responsibilty on Android.
NOTREACHED();
return UploadResult::kPermanentFailure;
#else
std::map<std::string, std::string> parameters; std::map<std::string, std::string> parameters;
FileReader* reader = report->Reader(); FileReader* reader = report->Reader();
@ -338,7 +332,6 @@ CrashReportUploadThread::UploadResult CrashReportUploadThread::UploadReport(
} }
return UploadResult::kSuccess; return UploadResult::kSuccess;
#endif // OS_ANDROID
} }
void CrashReportUploadThread::DoWork(const WorkerThread* thread) { void CrashReportUploadThread::DoWork(const WorkerThread* thread) {

View File

@ -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 Either this option or **--mach-service**, but not both, is required. This
option is only valid on macOS. 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* * **--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. 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 client to register, and exits when all clients have exited, after waiting for
any uploads in progress to complete. 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_ * **--mach-service**=_SERVICE_
Check in with the bootstrap server under the name _SERVICE_. Either this 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 To prevent excessive accumulation of handler processes, _ARGUMENT_ must not
be `--monitor-self`. 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** * **--no-periodic-tasks**
Do not scan for new pending crash reports or prune the crash report database. 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 dont accept uploads compressed in this for use with collection servers that dont accept uploads compressed in this
way. 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_ * **--pipe-name**=_PIPE_
Listen on the given pipe name for connections from clients. _PIPE_ must be of 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 parent process. This option is only valid on macOS. Use of this option is
discouraged. It should not be used absent extraordinary circumstances. 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_ * **--trace-parent-with-exception**=_EXCEPTION-INFORMATION-ADDRESS_
Causes the handler process to trace its parent process and exit. The parent Causes the handler process to trace its parent process and exit. The parent
process should have an ExceptionInformation struct at process should have an ExceptionInformation struct at
_EXCEPTION-INFORMATION-ADDRESS_. _EXCEPTION-INFORMATION-ADDRESS_. This option is only valid on Linux
platforms.
* **--initial-client-fd**=_FD_
Starts the excetion handler server with an initial ExceptionHandlerClient
connected on the socket _FD_. The server will exit when all connected client
sockets have been closed.
* **--url**=_URL_ * **--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 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. 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** * **--help**
Display help and exit. Display help and exit.

View File

@ -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"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with 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 // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#include "util/net/http_transport.h" #include "handler/handler_main.h"
#include "base/logging.h"
namespace crashpad { namespace crashpad {
std::unique_ptr<HTTPTransport> HTTPTransport::Create() { extern "C" {
NOTREACHED(); // TODO(scottmg): https://crashpad.chromium.org/bug/196
return std::unique_ptr<HTTPTransport>(); __attribute__((visibility("default"), used)) int CrashpadHandlerMain(
int argc,
char* argv[]) {
return HandlerMain(argc, argv, nullptr);
} }
} // extern "C"
} // namespace crashpad } // namespace crashpad

View File

@ -1,182 +0,0 @@
// Copyright 2017 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "handler/fuchsia/crash_report_exception_handler.h"
#include <zircon/syscalls/exception.h>
#include "base/fuchsia/fuchsia_logging.h"
#include "client/settings.h"
#include "minidump/minidump_file_writer.h"
#include "minidump/minidump_user_extension_stream_data_source.h"
#include "snapshot/fuchsia/process_snapshot_fuchsia.h"
#include "util/fuchsia/koid_utilities.h"
#include "util/fuchsia/scoped_task_suspend.h"
namespace crashpad {
namespace {
struct ScopedZxTaskResumeAfterException {
ScopedZxTaskResumeAfterException(zx_handle_t thread) : thread_(thread) {}
~ScopedZxTaskResumeAfterException() {
DCHECK_NE(thread_, ZX_HANDLE_INVALID);
// Resuming with ZX_RESUME_TRY_NEXT chains to the next handler. In normal
// operation, there won't be another beyond this one, which will result in
// the kernel terminating the process.
zx_status_t status =
zx_task_resume(thread_, ZX_RESUME_EXCEPTION | ZX_RESUME_TRY_NEXT);
if (status != ZX_OK) {
ZX_LOG(ERROR, status) << "zx_task_resume";
}
}
private:
zx_handle_t thread_; // weak
};
} // namespace
CrashReportExceptionHandler::CrashReportExceptionHandler(
CrashReportDatabase* database,
CrashReportUploadThread* upload_thread,
const std::map<std::string, std::string>* process_annotations,
const std::map<std::string, base::FilePath>* process_attachments,
const UserStreamDataSources* user_stream_data_sources)
: database_(database),
upload_thread_(upload_thread),
process_annotations_(process_annotations),
process_attachments_(process_attachments),
user_stream_data_sources_(user_stream_data_sources) {}
CrashReportExceptionHandler::~CrashReportExceptionHandler() {}
bool CrashReportExceptionHandler::HandleException(uint64_t process_id,
uint64_t thread_id) {
// TODO(scottmg): This function needs to be instrumented with metrics calls,
// https://crashpad.chromium.org/bug/230.
base::ScopedZxHandle process(GetProcessFromKoid(process_id));
if (!process.is_valid()) {
// There's no way to zx_task_resume() the thread if the process retrieval
// fails. Assume that the process has been already killed, and bail.
return false;
}
base::ScopedZxHandle thread(GetChildHandleByKoid(process.get(), thread_id));
if (!thread.is_valid()) {
return false;
}
return HandleExceptionHandles(process.get(), thread.get());
}
bool CrashReportExceptionHandler::HandleExceptionHandles(zx_handle_t process,
zx_handle_t thread) {
// Now that the thread has been successfully retrieved, it is possible to
// correctly call zx_task_resume() to continue exception processing, even if
// something else during this function fails.
ScopedZxTaskResumeAfterException resume(thread);
ProcessSnapshotFuchsia process_snapshot;
if (!process_snapshot.Initialize(process)) {
return false;
}
CrashpadInfoClientOptions client_options;
process_snapshot.GetCrashpadOptions(&client_options);
if (client_options.crashpad_handler_behavior != TriState::kDisabled) {
zx_exception_report_t report;
zx_status_t status = zx_object_get_info(thread,
ZX_INFO_THREAD_EXCEPTION_REPORT,
&report,
sizeof(report),
nullptr,
nullptr);
if (status != ZX_OK) {
ZX_LOG(ERROR, status)
<< "zx_object_get_info ZX_INFO_THREAD_EXCEPTION_REPORT";
return false;
}
zx_koid_t thread_id = GetKoidForHandle(thread);
if (!process_snapshot.InitializeException(thread_id, report)) {
return false;
}
UUID client_id;
Settings* const settings = database_->GetSettings();
if (settings) {
// If GetSettings() or GetClientID() fails, something else will log a
// message and client_id will be left at its default value, all zeroes,
// which is appropriate.
settings->GetClientID(&client_id);
}
process_snapshot.SetClientID(client_id);
process_snapshot.SetAnnotationsSimpleMap(*process_annotations_);
std::unique_ptr<CrashReportDatabase::NewReport> new_report;
CrashReportDatabase::OperationStatus database_status =
database_->PrepareNewCrashReport(&new_report);
if (database_status != CrashReportDatabase::kNoError) {
return false;
}
process_snapshot.SetReportID(new_report->ReportID());
MinidumpFileWriter minidump;
minidump.InitializeFromSnapshot(&process_snapshot);
AddUserExtensionStreams(
user_stream_data_sources_, &process_snapshot, &minidump);
if (!minidump.WriteEverything(new_report->Writer())) {
return false;
}
if (process_attachments_) {
// Note that attachments are read at this point each time rather than once
// so that if the contents of the file has changed it will be re-read for
// each upload (e.g. in the case of a log file).
for (const auto& it : *process_attachments_) {
FileWriter* writer = new_report->AddAttachment(it.first);
if (writer) {
std::string contents;
if (!LoggingReadEntireFile(it.second, &contents)) {
// Not being able to read the file isn't considered fatal, and
// should not prevent the report from being processed.
continue;
}
writer->Write(contents.data(), contents.size());
}
}
}
UUID uuid;
database_status =
database_->FinishedWritingCrashReport(std::move(new_report), &uuid);
if (database_status != CrashReportDatabase::kNoError) {
return false;
}
if (upload_thread_) {
upload_thread_->ReportPending(uuid);
}
}
return true;
}
} // namespace crashpad

View File

@ -1,105 +0,0 @@
// Copyright 2017 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef CRASHPAD_HANDLER_FUCHSIA_CRASH_REPORT_EXCEPTION_HANDLER_H_
#define CRASHPAD_HANDLER_FUCHSIA_CRASH_REPORT_EXCEPTION_HANDLER_H_
#include <stdint.h>
#include <zircon/types.h>
#include <map>
#include <string>
#include "base/files/file_path.h"
#include "base/macros.h"
#include "client/crash_report_database.h"
#include "handler/crash_report_upload_thread.h"
#include "handler/user_stream_data_source.h"
namespace crashpad {
//! \brief An exception handler that writes crash reports for exception messages
//! to a CrashReportDatabase.
class CrashReportExceptionHandler {
public:
//! \brief Creates a new object that will store crash reports in \a database.
//!
//! \param[in] database The database to store crash reports in. Weak.
//! \param[in] upload_thread The upload thread to notify when a new crash
//! report is written into \a database.
//! \param[in] process_annotations A map of annotations to insert as
//! process-level annotations into each crash report that is written. Do
//! not confuse this with module-level annotations, which are under the
//! control of the crashing process, and are used to implement Chrome's
//! "crash keys." Process-level annotations are those that are beyond the
//! control of the crashing process, which must reliably be set even if
//! the process crashes before its able to establish its own annotations.
//! To interoperate with Breakpad servers, the recommended practice is to
//! specify values for the `"prod"` and `"ver"` keys as process
//! annotations.
//! \param[in] process_attachments A map of file name keys to file paths to be
//! included in the report. Each time a report is written, the file paths
//! will be read in their entirety and included in the report using the
//! file name key as the name in the http upload.
//! \param[in] user_stream_data_sources Data sources to be used to extend
//! crash reports. For each crash report that is written, the data sources
//! are called in turn. These data sources may contribute additional
//! minidump streams. `nullptr` if not required.
CrashReportExceptionHandler(
CrashReportDatabase* database,
CrashReportUploadThread* upload_thread,
const std::map<std::string, std::string>* process_annotations,
const std::map<std::string, base::FilePath>* process_attachments,
const UserStreamDataSources* user_stream_data_sources);
~CrashReportExceptionHandler();
//! \brief Called when the exception handler server has caught an exception
//! and wants a crash dump to be taken.
//!
//! This function is expected to call `zx_task_resume()` in order to complete
//! handling of the exception.
//!
//! \param[in] process_id The koid of the process which sustained the
//! exception.
//! \param[in] thread_id The koid of the thread which sustained the exception.
//! \return `true` on success, or `false` with an error logged.
bool HandleException(uint64_t process_id, uint64_t thread_id);
//! \brief Called when the exception handler server has caught an exception
//! and wants a crash dump to be taken.
//!
//! This function is expected to call `zx_task_resume()` in order to complete
//! handling of the exception.
//!
//! \param[in] process The handle to the process which sustained the
//! exception.
//! \param[in] thread The handle to the thread of \a process which sustained
//! the exception.
//! \return `true` on success, or `false` with an error logged.
bool HandleExceptionHandles(zx_handle_t process, zx_handle_t thread);
private:
CrashReportDatabase* database_; // weak
CrashReportUploadThread* upload_thread_; // weak
const std::map<std::string, std::string>* process_annotations_; // weak
const std::map<std::string, base::FilePath>* process_attachments_; // weak
const UserStreamDataSources* user_stream_data_sources_; // weak
DISALLOW_COPY_AND_ASSIGN(CrashReportExceptionHandler);
};
} // namespace crashpad
#endif // CRASHPAD_HANDLER_FUCHSIA_CRASH_REPORT_EXCEPTION_HANDLER_H_

View File

@ -1,60 +0,0 @@
// Copyright 2017 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "handler/fuchsia/exception_handler_server.h"
#include <zircon/syscalls/exception.h>
#include <zircon/syscalls/port.h>
#include <utility>
#include "base/fuchsia/fuchsia_logging.h"
#include "base/logging.h"
#include "handler/fuchsia/crash_report_exception_handler.h"
#include "util/fuchsia/system_exception_port_key.h"
namespace crashpad {
ExceptionHandlerServer::ExceptionHandlerServer(
base::ScopedZxHandle root_job,
base::ScopedZxHandle exception_port)
: root_job_(std::move(root_job)),
exception_port_(std::move(exception_port)) {}
ExceptionHandlerServer::~ExceptionHandlerServer() = default;
void ExceptionHandlerServer::Run(CrashReportExceptionHandler* handler) {
while (true) {
zx_port_packet_t packet;
zx_status_t status =
zx_port_wait(exception_port_.get(), ZX_TIME_INFINITE, &packet);
if (status != ZX_OK) {
ZX_LOG(ERROR, status) << "zx_port_wait, aborting";
return;
}
if (packet.key != kSystemExceptionPortKey) {
LOG(ERROR) << "unexpected packet key, ignoring";
continue;
}
bool result =
handler->HandleException(packet.exception.pid, packet.exception.tid);
if (!result) {
LOG(ERROR) << "HandleException failed";
}
}
}
} // namespace crashpad

View File

@ -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_

View File

@ -28,6 +28,7 @@
'../minidump/minidump.gyp:crashpad_minidump', '../minidump/minidump.gyp:crashpad_minidump',
'../snapshot/snapshot.gyp:crashpad_snapshot', '../snapshot/snapshot.gyp:crashpad_snapshot',
'../third_party/mini_chromium/mini_chromium.gyp:base', '../third_party/mini_chromium/mini_chromium.gyp:base',
'../third_party/zlib/zlib.gyp:zlib',
'../tools/tools.gyp:crashpad_tool_support', '../tools/tools.gyp:crashpad_tool_support',
'../util/util.gyp:crashpad_util', '../util/util.gyp:crashpad_util',
], ],
@ -39,6 +40,8 @@
'crash_report_upload_thread.h', 'crash_report_upload_thread.h',
'handler_main.cc', 'handler_main.cc',
'handler_main.h', 'handler_main.h',
'linux/capture_snapshot.cc',
'linux/capture_snapshot.h',
'linux/crash_report_exception_handler.cc', 'linux/crash_report_exception_handler.cc',
'linux/crash_report_exception_handler.h', 'linux/crash_report_exception_handler.h',
'linux/exception_handler_server.cc', 'linux/exception_handler_server.cc',

View File

@ -56,6 +56,10 @@
#include "util/string/split_string.h" #include "util/string/split_string.h"
#include "util/synchronization/semaphore.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) #if defined(OS_LINUX) || defined(OS_ANDROID)
#include <unistd.h> #include <unistd.h>
@ -70,8 +74,8 @@
#include "handler/mac/crash_report_exception_handler.h" #include "handler/mac/crash_report_exception_handler.h"
#include "handler/mac/exception_handler_server.h" #include "handler/mac/exception_handler_server.h"
#include "handler/mac/file_limit_annotation.h" #include "handler/mac/file_limit_annotation.h"
#include "util/mach/bootstrap.h"
#include "util/mach/child_port_handshake.h" #include "util/mach/child_port_handshake.h"
#include "util/mach/mach_extensions.h"
#include "util/posix/close_stdio.h" #include "util/posix/close_stdio.h"
#include "util/posix/signals.h" #include "util/posix/signals.h"
#elif defined(OS_WIN) #elif defined(OS_WIN)
@ -82,12 +86,6 @@
#include "util/win/handle.h" #include "util/win/handle.h"
#include "util/win/initial_client_data.h" #include "util/win/initial_client_data.h"
#include "util/win/session_end_watcher.h" #include "util/win/session_end_watcher.h"
#elif defined(OS_FUCHSIA)
#include <zircon/process.h>
#include <zircon/processargs.h>
#include "handler/fuchsia/crash_report_exception_handler.h"
#include "handler/fuchsia/exception_handler_server.h"
#elif defined(OS_LINUX) #elif defined(OS_LINUX)
#include "handler/linux/crash_report_exception_handler.h" #include "handler/linux/crash_report_exception_handler.h"
#include "handler/linux/exception_handler_server.h" #include "handler/linux/exception_handler_server.h"
@ -118,6 +116,9 @@ void Usage(const base::FilePath& me) {
" Address_debug_critical_section\n" " Address_debug_critical_section\n"
" use precreated data to register initial client\n" " use precreated data to register initial client\n"
#endif // OS_WIN #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) #if defined(OS_MACOSX)
" --mach-service=SERVICE register SERVICE with the bootstrap server\n" " --mach-service=SERVICE register SERVICE with the bootstrap server\n"
#endif // OS_MACOSX #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-periodic-tasks don't scan for new reports or prune the database\n"
" --no-rate-limit don't rate limit crash uploads\n" " --no-rate-limit don't rate limit crash uploads\n"
" --no-upload-gzip don't use gzip compression when uploading\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) #if defined(OS_WIN)
" --pipe-name=PIPE communicate with the client over PIPE\n" " --pipe-name=PIPE communicate with the client over PIPE\n"
#endif // OS_WIN #endif // OS_WIN
@ -141,14 +146,31 @@ void Usage(const base::FilePath& me) {
" reset the server's exception handler to default\n" " reset the server's exception handler to default\n"
#endif // OS_MACOSX #endif // OS_MACOSX
#if defined(OS_LINUX) || defined(OS_ANDROID) #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" " --trace-parent-with-exception=EXCEPTION_INFORMATION_ADDRESS\n"
" request a dump for the handler's parent process\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 #endif // OS_LINUX || OS_ANDROID
" --url=URL send crash reports to this Breakpad server URL,\n" " --url=URL send crash reports to this Breakpad server URL,\n"
" only if uploads are enabled for the database\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" " --help display this help and exit\n"
" --version output version information and exit\n", " --version output version information and exit\n",
me.value().c_str()); me.value().c_str());
@ -168,8 +190,13 @@ struct Options {
bool reset_own_crash_exception_port_to_system_default; bool reset_own_crash_exception_port_to_system_default;
#elif defined(OS_LINUX) || defined(OS_ANDROID) #elif defined(OS_LINUX) || defined(OS_ANDROID)
VMAddress exception_information_address; VMAddress exception_information_address;
int initial_client_fd;
VMAddress sanitization_information_address; 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) #elif defined(OS_WIN)
std::string pipe_name; std::string pipe_name;
InitialClientData initial_client_data; InitialClientData initial_client_data;
@ -179,6 +206,11 @@ struct Options {
bool periodic_tasks; bool periodic_tasks;
bool rate_limit; bool rate_limit;
bool upload_gzip; 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|. // 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) #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) { void HandleCrashSignal(int sig, siginfo_t* siginfo, void* context) {
MetricsRecordExit(Metrics::LifetimeMilestone::kCrashed); MetricsRecordExit(Metrics::LifetimeMilestone::kCrashed);
@ -274,9 +304,7 @@ void HandleCrashSignal(int sig, siginfo_t* siginfo, void* context) {
} }
Metrics::HandlerCrashed(metrics_code); Metrics::HandlerCrashed(metrics_code);
struct sigaction* old_action = Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, nullptr);
g_old_crash_signal_handlers.ActionForSignal(sig);
Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, old_action);
} }
void HandleTerminateSignal(int sig, siginfo_t* siginfo, void* context) { 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); Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, nullptr);
} }
#if defined(OS_MACOSX)
void ReinstallCrashHandler() { void ReinstallCrashHandler() {
// This is used to re-enable the metrics-recording crash handler after // This is used to re-enable the metrics-recording crash handler after
// MonitorSelf() sets up a Crashpad exception handler. On macOS, the // MonitorSelf() sets up a Crashpad exception handler. On macOS, the
// metrics-recording handler uses signals and the Crashpad handler uses Mach // metrics-recording handler uses signals and the Crashpad handler uses Mach
// exceptions, so theres nothing to re-enable. // exceptions, so theres nothing to re-enable.
// On Linux, the signal handler installed by StartHandler() restores the
// previously installed signal handler by default.
} }
void InstallCrashHandler() { void InstallCrashHandler() {
@ -300,6 +328,8 @@ void InstallCrashHandler() {
Signals::InstallTerminateHandlers(HandleTerminateSignal, 0, nullptr); Signals::InstallTerminateHandlers(HandleTerminateSignal, 0, nullptr);
} }
#if defined(OS_MACOSX)
struct ResetSIGTERMTraits { struct ResetSIGTERMTraits {
static struct sigaction* InvalidValue() { static struct sigaction* InvalidValue() {
return nullptr; return nullptr;
@ -324,21 +354,6 @@ void HandleSIGTERM(int sig, siginfo_t* siginfo, void* context) {
g_exception_handler_server->Stop(); 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 #endif // OS_MACOSX
#elif defined(OS_WIN) #elif defined(OS_WIN)
@ -396,22 +411,6 @@ void InstallCrashHandler() {
ALLOW_UNUSED_LOCAL(terminate_handler); 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 #endif // OS_MACOSX
void MonitorSelf(const Options& options) { 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 // instance of crashpad_handler to be writing metrics at a time, and it should
// be the primary instance. // be the primary instance.
CrashpadClient crashpad_client; CrashpadClient crashpad_client;
#if defined(OS_LINUX) || defined(OS_ANDROID) #if defined(OS_ANDROID)
if (!crashpad_client.StartHandlerAtCrash(executable_path, if (!crashpad_client.StartHandlerAtCrash(executable_path,
options.database, options.database,
base::FilePath(), base::FilePath(),
@ -500,6 +499,12 @@ class ScopedStoppable {
int HandlerMain(int argc, int HandlerMain(int argc,
char* argv[], char* argv[],
const UserStreamDataSources* user_stream_sources) { 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(); InstallCrashHandler();
CallMetricsRecordNormalExit metrics_record_normal_exit; CallMetricsRecordNormalExit metrics_record_normal_exit;
@ -518,6 +523,9 @@ int HandlerMain(int argc,
#if defined(OS_WIN) #if defined(OS_WIN)
kOptionInitialClientData, kOptionInitialClientData,
#endif // OS_WIN #endif // OS_WIN
#if defined(OS_ANDROID) || defined(OS_LINUX)
kOptionInitialClientFD,
#endif // OS_ANDROID || OS_LINUX
#if defined(OS_MACOSX) #if defined(OS_MACOSX)
kOptionMachService, kOptionMachService,
#endif // OS_MACOSX #endif // OS_MACOSX
@ -529,6 +537,9 @@ int HandlerMain(int argc,
kOptionNoPeriodicTasks, kOptionNoPeriodicTasks,
kOptionNoRateLimit, kOptionNoRateLimit,
kOptionNoUploadGzip, kOptionNoUploadGzip,
#if defined(OS_ANDROID)
kOptionNoWriteMinidumpToDatabase,
#endif // OS_ANDROID
#if defined(OS_WIN) #if defined(OS_WIN)
kOptionPipeName, kOptionPipeName,
#endif // OS_WIN #endif // OS_WIN
@ -536,11 +547,19 @@ int HandlerMain(int argc,
kOptionResetOwnCrashExceptionPortToSystemDefault, kOptionResetOwnCrashExceptionPortToSystemDefault,
#endif // OS_MACOSX #endif // OS_MACOSX
#if defined(OS_LINUX) || defined(OS_ANDROID) #if defined(OS_LINUX) || defined(OS_ANDROID)
kOptionTraceParentWithException,
kOptionInitialClientFD,
kOptionSanitizationInformation, kOptionSanitizationInformation,
kOptionSharedClientConnection,
kOptionTraceParentWithException,
#endif #endif
kOptionURL, kOptionURL,
#if defined(OS_CHROMEOS)
kOptionUseCrosCrashReporter,
kOptionMinidumpDirForTests,
kOptionAlwaysAllowFeedback,
#endif // OS_CHROMEOS
#if defined(OS_ANDROID)
kOptionWriteMinidumpToLog,
#endif // OS_ANDROID
// Standard options. // Standard options.
kOptionHelp = -2, kOptionHelp = -2,
@ -559,6 +578,9 @@ int HandlerMain(int argc,
nullptr, nullptr,
kOptionInitialClientData}, kOptionInitialClientData},
#endif // OS_MACOSX #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) #if defined(OS_MACOSX)
{"mach-service", required_argument, nullptr, kOptionMachService}, {"mach-service", required_argument, nullptr, kOptionMachService},
#endif // OS_MACOSX #endif // OS_MACOSX
@ -579,6 +601,12 @@ int HandlerMain(int argc,
{"no-periodic-tasks", no_argument, nullptr, kOptionNoPeriodicTasks}, {"no-periodic-tasks", no_argument, nullptr, kOptionNoPeriodicTasks},
{"no-rate-limit", no_argument, nullptr, kOptionNoRateLimit}, {"no-rate-limit", no_argument, nullptr, kOptionNoRateLimit},
{"no-upload-gzip", no_argument, nullptr, kOptionNoUploadGzip}, {"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) #if defined(OS_WIN)
{"pipe-name", required_argument, nullptr, kOptionPipeName}, {"pipe-name", required_argument, nullptr, kOptionPipeName},
#endif // OS_WIN #endif // OS_WIN
@ -589,17 +617,37 @@ int HandlerMain(int argc,
kOptionResetOwnCrashExceptionPortToSystemDefault}, kOptionResetOwnCrashExceptionPortToSystemDefault},
#endif // OS_MACOSX #endif // OS_MACOSX
#if defined(OS_LINUX) || defined(OS_ANDROID) #if defined(OS_LINUX) || defined(OS_ANDROID)
{"trace-parent-with-exception",
required_argument,
nullptr,
kOptionTraceParentWithException},
{"initial-client-fd", required_argument, nullptr, kOptionInitialClientFD},
{"sanitization-information", {"sanitization-information",
required_argument, required_argument,
nullptr, nullptr,
kOptionSanitizationInformation}, kOptionSanitizationInformation},
{"shared-client-connection",
no_argument,
nullptr,
kOptionSharedClientConnection},
{"trace-parent-with-exception",
required_argument,
nullptr,
kOptionTraceParentWithException},
#endif // OS_LINUX || OS_ANDROID #endif // OS_LINUX || OS_ANDROID
{"url", required_argument, nullptr, kOptionURL}, {"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}, {"help", no_argument, nullptr, kOptionHelp},
{"version", no_argument, nullptr, kOptionVersion}, {"version", no_argument, nullptr, kOptionVersion},
{nullptr, 0, nullptr, 0}, {nullptr, 0, nullptr, 0},
@ -610,13 +658,14 @@ int HandlerMain(int argc,
options.handshake_fd = -1; options.handshake_fd = -1;
#endif #endif
options.identify_client_via_url = true; options.identify_client_via_url = true;
#if defined(OS_LINUX) || defined(OS_ANDROID)
options.initial_client_fd = kInvalidFileHandle;
#endif
options.periodic_tasks = true; options.periodic_tasks = true;
options.rate_limit = true; options.rate_limit = true;
options.upload_gzip = true; options.upload_gzip = true;
#if defined(OS_LINUX) || defined(OS_ANDROID) #if defined(OS_ANDROID)
options.exception_information_address = 0; options.write_minidump_to_database = true;
options.initial_client_fd = kInvalidFileHandle;
options.sanitization_information_address = 0;
#endif #endif
int opt; int opt;
@ -658,6 +707,15 @@ int HandlerMain(int argc,
break; break;
} }
#endif // OS_WIN #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: { case kOptionMetrics: {
options.metrics_dir = base::FilePath( options.metrics_dir = base::FilePath(
ToolSupport::CommandLineArgumentToFilePathStringType(optarg)); ToolSupport::CommandLineArgumentToFilePathStringType(optarg));
@ -695,6 +753,12 @@ int HandlerMain(int argc,
options.upload_gzip = false; options.upload_gzip = false;
break; break;
} }
#if defined(OS_ANDROID)
case kOptionNoWriteMinidumpToDatabase: {
options.write_minidump_to_database = false;
break;
}
#endif // OS_ANDROID
#if defined(OS_WIN) #if defined(OS_WIN)
case kOptionPipeName: { case kOptionPipeName: {
options.pipe_name = optarg; options.pipe_name = optarg;
@ -708,21 +772,6 @@ int HandlerMain(int argc,
} }
#endif // OS_MACOSX #endif // OS_MACOSX
#if defined(OS_LINUX) || defined(OS_ANDROID) #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: { case kOptionSanitizationInformation: {
if (!StringToNumber(optarg, if (!StringToNumber(optarg,
&options.sanitization_information_address)) { &options.sanitization_information_address)) {
@ -732,11 +781,44 @@ int HandlerMain(int argc,
} }
break; 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 #endif // OS_LINUX || OS_ANDROID
case kOptionURL: { case kOptionURL: {
options.url = optarg; options.url = optarg;
break; 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: { case kOptionHelp: {
Usage(me); Usage(me);
MetricsRecordExit(Metrics::LifetimeMilestone::kExitedEarly); MetricsRecordExit(Metrics::LifetimeMilestone::kExitedEarly);
@ -781,7 +863,7 @@ int HandlerMain(int argc,
if (!options.exception_information_address && if (!options.exception_information_address &&
options.initial_client_fd == kInvalidFileHandle) { options.initial_client_fd == kInvalidFileHandle) {
ToolSupport::UsageHint( 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(); return ExitFailure();
} }
if (options.sanitization_information_address && if (options.sanitization_information_address &&
@ -791,6 +873,20 @@ int HandlerMain(int argc,
"--sanitization_information requires --trace-parent-with-exception"); "--sanitization_information requires --trace-parent-with-exception");
return ExitFailure(); 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 #endif // OS_MACOSX
if (options.database.empty()) { if (options.database.empty()) {
@ -856,23 +952,61 @@ int HandlerMain(int argc,
upload_thread.Get()->Start(); upload_thread.Get()->Start();
} }
CrashReportExceptionHandler exception_handler( #if defined(OS_LINUX) || defined(OS_ANDROID)
std::unique_ptr<ExceptionHandlerServer::Delegate> exception_handler;
#else
std::unique_ptr<CrashReportExceptionHandler> exception_handler;
#endif
#if defined(OS_CHROMEOS)
if (options.use_cros_crash_reporter) {
auto cros_handler = std::make_unique<CrosCrashReportExceptionHandler>(
database.get(),
&options.annotations,
user_stream_sources);
if (!options.minidump_dir_for_tests.empty()) {
cros_handler->SetDumpDir(options.minidump_dir_for_tests);
}
if (options.always_allow_feedback) {
cros_handler->SetAlwaysAllowFeedback();
}
exception_handler = std::move(cros_handler);
} else {
exception_handler = std::make_unique<CrashReportExceptionHandler>(
database.get(), database.get(),
static_cast<CrashReportUploadThread*>(upload_thread.Get()), static_cast<CrashReportUploadThread*>(upload_thread.Get()),
&options.annotations, &options.annotations,
#if defined(OS_FUCHSIA) true,
// TODO(scottmg): Process level file attachments, and for all platforms. false,
nullptr,
#endif
user_stream_sources); user_stream_sources);
}
#else
exception_handler = std::make_unique<CrashReportExceptionHandler>(
database.get(),
static_cast<CrashReportUploadThread*>(upload_thread.Get()),
&options.annotations,
#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) { if (options.exception_information_address) {
ClientInformation info; ExceptionHandlerProtocol::ClientInformation info;
info.exception_information_address = options.exception_information_address; info.exception_information_address = options.exception_information_address;
info.sanitization_information_address = info.sanitization_information_address =
options.sanitization_information_address; options.sanitization_information_address;
return exception_handler.HandleException(getppid(), info) ? EXIT_SUCCESS return exception_handler->HandleException(getppid(), geteuid(), info)
? EXIT_SUCCESS
: ExitFailure(); : ExitFailure();
} }
#endif // OS_LINUX || OS_ANDROID #endif // OS_LINUX || OS_ANDROID
@ -937,27 +1071,6 @@ int HandlerMain(int argc,
if (!options.pipe_name.empty()) { if (!options.pipe_name.empty()) {
exception_handler_server.SetPipeName(base::UTF8ToUTF16(options.pipe_name)); 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) #elif defined(OS_LINUX) || defined(OS_ANDROID)
ExceptionHandlerServer exception_handler_server; ExceptionHandlerServer exception_handler_server;
#endif // OS_MACOSX #endif // OS_MACOSX
@ -978,17 +1091,18 @@ int HandlerMain(int argc,
#if defined(OS_WIN) #if defined(OS_WIN)
if (options.initial_client_data.IsValid()) { if (options.initial_client_data.IsValid()) {
exception_handler_server.InitializeWithInheritedDataForInitialClient( exception_handler_server.InitializeWithInheritedDataForInitialClient(
options.initial_client_data, &exception_handler); options.initial_client_data, exception_handler.get());
} }
#elif defined(OS_LINUX) || defined(OS_ANDROID) #elif defined(OS_LINUX) || defined(OS_ANDROID)
if (options.initial_client_fd == kInvalidFileHandle || if (options.initial_client_fd == kInvalidFileHandle ||
!exception_handler_server.InitializeWithClient( !exception_handler_server.InitializeWithClient(
ScopedFileHandle(options.initial_client_fd))) { ScopedFileHandle(options.initial_client_fd),
options.shared_client_connection)) {
return ExitFailure(); return ExitFailure();
} }
#endif // OS_WIN #endif // OS_WIN
exception_handler_server.Run(&exception_handler); exception_handler_server.Run(exception_handler.get());
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }

View File

@ -15,6 +15,7 @@
#ifndef CRASHPAD_HANDLER_HANDLER_MAIN_H_ #ifndef CRASHPAD_HANDLER_HANDLER_MAIN_H_
#define CRASHPAD_HANDLER_HANDLER_MAIN_H_ #define CRASHPAD_HANDLER_HANDLER_MAIN_H_
#include "build/build_config.h"
#include "handler/user_stream_data_source.h" #include "handler/user_stream_data_source.h"
namespace crashpad { namespace crashpad {
@ -34,6 +35,16 @@ int HandlerMain(int argc,
char* argv[], char* argv[],
const UserStreamDataSources* user_stream_sources); 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 } // namespace crashpad
#endif // CRASHPAD_HANDLER_HANDLER_MAIN_H_ #endif // CRASHPAD_HANDLER_HANDLER_MAIN_H_

View File

@ -0,0 +1,119 @@
// Copyright 2019 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "handler/linux/capture_snapshot.h"
#include <utility>
#include "snapshot/crashpad_info_client_options.h"
#include "snapshot/sanitized/sanitization_information.h"
#include "util/misc/metrics.h"
#include "util/misc/tri_state.h"
namespace crashpad {
bool CaptureSnapshot(
PtraceConnection* connection,
const ExceptionHandlerProtocol::ClientInformation& info,
const std::map<std::string, std::string>& process_annotations,
uid_t client_uid,
VMAddress requesting_thread_stack_address,
pid_t* requesting_thread_id,
std::unique_ptr<ProcessSnapshotLinux>* snapshot,
std::unique_ptr<ProcessSnapshotSanitized>* sanitized_snapshot) {
std::unique_ptr<ProcessSnapshotLinux> process_snapshot(
new ProcessSnapshotLinux());
if (!process_snapshot->Initialize(connection)) {
Metrics::ExceptionCaptureResult(Metrics::CaptureResult::kSnapshotFailed);
return false;
}
pid_t local_requesting_thread_id = -1;
if (requesting_thread_stack_address) {
local_requesting_thread_id = process_snapshot->FindThreadWithStackAddress(
requesting_thread_stack_address);
}
if (requesting_thread_id) {
*requesting_thread_id = local_requesting_thread_id;
}
if (!process_snapshot->InitializeException(info.exception_information_address,
local_requesting_thread_id)) {
Metrics::ExceptionCaptureResult(
Metrics::CaptureResult::kExceptionInitializationFailed);
return false;
}
Metrics::ExceptionCode(process_snapshot->Exception()->Exception());
CrashpadInfoClientOptions client_options;
process_snapshot->GetCrashpadOptions(&client_options);
if (client_options.crashpad_handler_behavior == TriState::kDisabled) {
return false;
}
for (auto& p : process_annotations) {
process_snapshot->AddAnnotation(p.first, p.second);
}
if (info.sanitization_information_address) {
SanitizationInformation sanitization_info;
ProcessMemoryRange range;
if (!range.Initialize(connection->Memory(), connection->Is64Bit()) ||
!range.Read(info.sanitization_information_address,
sizeof(sanitization_info),
&sanitization_info)) {
Metrics::ExceptionCaptureResult(
Metrics::CaptureResult::kSanitizationInitializationFailed);
return false;
}
auto annotations_whitelist = std::make_unique<std::vector<std::string>>();
auto memory_range_whitelist =
std::make_unique<std::vector<std::pair<VMAddress, VMAddress>>>();
if (!ReadAnnotationsWhitelist(
range,
sanitization_info.annotations_whitelist_address,
annotations_whitelist.get()) ||
!ReadMemoryRangeWhitelist(
range,
sanitization_info.memory_range_whitelist_address,
memory_range_whitelist.get())) {
Metrics::ExceptionCaptureResult(
Metrics::CaptureResult::kSanitizationInitializationFailed);
return false;
}
std::unique_ptr<ProcessSnapshotSanitized> sanitized(
new ProcessSnapshotSanitized());
if (!sanitized->Initialize(process_snapshot.get(),
sanitization_info.annotations_whitelist_address
? std::move(annotations_whitelist)
: nullptr,
std::move(memory_range_whitelist),
sanitization_info.target_module_address,
sanitization_info.sanitize_stacks)) {
Metrics::ExceptionCaptureResult(
Metrics::CaptureResult::kSkippedDueToSanitization);
return false;
}
*sanitized_snapshot = std::move(sanitized);
}
*snapshot = std::move(process_snapshot);
return true;
}
} // namespace crashpad

View File

@ -0,0 +1,67 @@
// Copyright 2019 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef CRASHPAD_HANDLER_LINUX_CAPTURE_SNAPSHOT_H_
#define CRASHPAD_HANDLER_LINUX_CAPTURE_SNAPSHOT_H_
#include <sys/types.h>
#include <map>
#include <memory>
#include <string>
#include "snapshot/linux/process_snapshot_linux.h"
#include "snapshot/sanitized/process_snapshot_sanitized.h"
#include "util/linux/exception_handler_protocol.h"
#include "util/linux/ptrace_connection.h"
#include "util/misc/address_types.h"
namespace crashpad {
//! \brief Captures a snapshot of a client over \a connection.
//!
//! \param[in] connection A PtraceConnection to the client to snapshot.
//! \param[in] info Information about the client configuring the snapshot.
//! \param[in] process_annotations A map of annotations to insert as
//! process-level annotations into the snapshot.
//! \param[in] client_uid The client's user ID.
//! \param[in] requesting_thread_stack_address An address on the stack of the
//! thread requesting the snapshot. If \a info includes an exception
//! address, the exception will be assigned to the thread whose stack
//! address range contains this address. If 0, \a requesting_thread_id will
//! be -1.
//! \param[out] requesting_thread_id The thread ID of the thread corresponding
//! to \a requesting_thread_stack_address. Set to -1 if the thread ID could
//! not be determined. Optional.
//! \param[out] process_snapshot A snapshot of the client process, valid if this
//! function returns `true`.
//! \param[out] sanitized_snapshot A sanitized snapshot of the client process,
//! valid if this function returns `true` and sanitization was requested in
//! \a info.
//! \return `true` if \a process_snapshot was successfully created. A message
//! will be logged on failure, but not if the snapshot was skipped because
//! handling was disabled by CrashpadInfoClientOptions.
bool CaptureSnapshot(
PtraceConnection* connection,
const ExceptionHandlerProtocol::ClientInformation& info,
const std::map<std::string, std::string>& process_annotations,
uid_t client_uid,
VMAddress requesting_thread_stack_address,
pid_t* requesting_thread_id,
std::unique_ptr<ProcessSnapshotLinux>* process_snapshot,
std::unique_ptr<ProcessSnapshotSanitized>* sanitized_snapshot);
} // namespace crashpad
#endif // CRASHPAD_HANDLER_LINUX_CAPTURE_SNAPSHOT_H_

View File

@ -14,38 +14,75 @@
#include "handler/linux/crash_report_exception_handler.h" #include "handler/linux/crash_report_exception_handler.h"
#include <vector> #include <memory>
#include <utility>
#include "base/logging.h" #include "base/logging.h"
#include "client/settings.h" #include "client/settings.h"
#include "handler/linux/capture_snapshot.h"
#include "minidump/minidump_file_writer.h" #include "minidump/minidump_file_writer.h"
#include "snapshot/crashpad_info_client_options.h"
#include "snapshot/linux/process_snapshot_linux.h" #include "snapshot/linux/process_snapshot_linux.h"
#include "snapshot/sanitized/process_snapshot_sanitized.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/direct_ptrace_connection.h"
#include "util/linux/ptrace_client.h" #include "util/linux/ptrace_client.h"
#include "util/misc/implicit_cast.h"
#include "util/misc/metrics.h" #include "util/misc/metrics.h"
#include "util/misc/tri_state.h"
#include "util/misc/uuid.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 crashpad {
namespace {
bool WriteMinidumpLogFromFile(FileReaderInterface* file_reader) {
ZlibOutputStream stream(ZlibOutputStream::Mode::kCompress,
std::make_unique<Base94OutputStream>(
Base94OutputStream::Mode::kEncode,
std::make_unique<LogOutputStream>()));
FileOperationResult read_result;
do {
uint8_t buffer[4096];
read_result = file_reader->Read(buffer, sizeof(buffer));
if (read_result < 0)
return false;
if (read_result > 0 && (!stream.Write(buffer, read_result)))
return false;
} while (read_result > 0);
return stream.Flush();
}
} // namespace
CrashReportExceptionHandler::CrashReportExceptionHandler( CrashReportExceptionHandler::CrashReportExceptionHandler(
CrashReportDatabase* database, CrashReportDatabase* database,
CrashReportUploadThread* upload_thread, CrashReportUploadThread* upload_thread,
const std::map<std::string, std::string>* process_annotations, const std::map<std::string, std::string>* process_annotations,
bool write_minidump_to_database,
bool write_minidump_to_log,
const UserStreamDataSources* user_stream_data_sources) const UserStreamDataSources* user_stream_data_sources)
: database_(database), : database_(database),
upload_thread_(upload_thread), upload_thread_(upload_thread),
process_annotations_(process_annotations), 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; CrashReportExceptionHandler::~CrashReportExceptionHandler() = default;
bool CrashReportExceptionHandler::HandleException( bool CrashReportExceptionHandler::HandleException(
pid_t client_process_id, 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(); Metrics::ExceptionEncountered();
DirectPtraceConnection connection; DirectPtraceConnection connection;
@ -55,13 +92,20 @@ bool CrashReportExceptionHandler::HandleException(
return false; 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( bool CrashReportExceptionHandler::HandleExceptionWithBroker(
pid_t client_process_id, pid_t client_process_id,
const ClientInformation& info, uid_t client_uid,
int broker_sock) { const ExceptionHandlerProtocol::ClientInformation& info,
int broker_sock,
UUID* local_report_id) {
Metrics::ExceptionEncountered(); Metrics::ExceptionEncountered();
PtraceClient client; PtraceClient client;
@ -71,30 +115,30 @@ bool CrashReportExceptionHandler::HandleExceptionWithBroker(
return false; return false;
} }
return HandleExceptionWithConnection(&client, info); return HandleExceptionWithConnection(
&client, info, client_uid, 0, nullptr, local_report_id);
} }
bool CrashReportExceptionHandler::HandleExceptionWithConnection( bool CrashReportExceptionHandler::HandleExceptionWithConnection(
PtraceConnection* connection, PtraceConnection* connection,
const ClientInformation& info) { const ExceptionHandlerProtocol::ClientInformation& info,
ProcessSnapshotLinux process_snapshot; uid_t client_uid,
if (!process_snapshot.Initialize(connection)) { VMAddress requesting_thread_stack_address,
Metrics::ExceptionCaptureResult(Metrics::CaptureResult::kSnapshotFailed); pid_t* requesting_thread_id,
UUID* local_report_id) {
std::unique_ptr<ProcessSnapshotLinux> process_snapshot;
std::unique_ptr<ProcessSnapshotSanitized> sanitized_snapshot;
if (!CaptureSnapshot(connection,
info,
*process_annotations_,
client_uid,
requesting_thread_stack_address,
requesting_thread_id,
&process_snapshot,
&sanitized_snapshot)) {
return false; return false;
} }
if (!process_snapshot.InitializeException(
info.exception_information_address)) {
Metrics::ExceptionCaptureResult(
Metrics::CaptureResult::kExceptionInitializationFailed);
return false;
}
Metrics::ExceptionCode(process_snapshot.Exception()->Exception());
CrashpadInfoClientOptions client_options;
process_snapshot.GetCrashpadOptions(&client_options);
if (client_options.crashpad_handler_behavior != TriState::kDisabled) {
UUID client_id; UUID client_id;
Settings* const settings = database_->GetSettings(); Settings* const settings = database_->GetSettings();
if (settings) { if (settings) {
@ -103,10 +147,22 @@ bool CrashReportExceptionHandler::HandleExceptionWithConnection(
// which is appropriate. // which is appropriate.
settings->GetClientID(&client_id); settings->GetClientID(&client_id);
} }
process_snapshot->SetClientID(client_id);
process_snapshot.SetClientID(client_id); return write_minidump_to_database_
process_snapshot.SetAnnotationsSimpleMap(*process_annotations_); ? WriteMinidumpToDatabase(process_snapshot.get(),
sanitized_snapshot.get(),
write_minidump_to_log_,
local_report_id)
: WriteMinidumpToLog(process_snapshot.get(),
sanitized_snapshot.get());
}
bool CrashReportExceptionHandler::WriteMinidumpToDatabase(
ProcessSnapshotLinux* process_snapshot,
ProcessSnapshotSanitized* sanitized_snapshot,
bool write_minidump_to_log,
UUID* local_report_id) {
std::unique_ptr<CrashReportDatabase::NewReport> new_report; std::unique_ptr<CrashReportDatabase::NewReport> new_report;
CrashReportDatabase::OperationStatus database_status = CrashReportDatabase::OperationStatus database_status =
database_->PrepareNewCrashReport(&new_report); database_->PrepareNewCrashReport(&new_report);
@ -117,48 +173,11 @@ bool CrashReportExceptionHandler::HandleExceptionWithConnection(
return false; return false;
} }
process_snapshot.SetReportID(new_report->ReportID()); process_snapshot->SetReportID(new_report->ReportID());
ProcessSnapshot* snapshot = nullptr; ProcessSnapshot* snapshot =
ProcessSnapshotSanitized sanitized; sanitized_snapshot ? implicit_cast<ProcessSnapshot*>(sanitized_snapshot)
std::vector<std::string> whitelist; : implicit_cast<ProcessSnapshot*>(process_snapshot);
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; MinidumpFileWriter minidump;
minidump.InitializeFromSnapshot(snapshot); minidump.InitializeFromSnapshot(snapshot);
@ -171,6 +190,16 @@ bool CrashReportExceptionHandler::HandleExceptionWithConnection(
return false; 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";
}
}
UUID uuid; UUID uuid;
database_status = database_status =
database_->FinishedWritingCrashReport(std::move(new_report), &uuid); database_->FinishedWritingCrashReport(std::move(new_report), &uuid);
@ -184,10 +213,36 @@ bool CrashReportExceptionHandler::HandleExceptionWithConnection(
if (upload_thread_) { if (upload_thread_) {
upload_thread_->ReportPending(uuid); upload_thread_->ReportPending(uuid);
} }
if (local_report_id != nullptr) {
*local_report_id = uuid;
} }
Metrics::ExceptionCaptureResult(Metrics::CaptureResult::kSuccess); Metrics::ExceptionCaptureResult(Metrics::CaptureResult::kSuccess);
return true;
return write_minidump_to_log ? write_minidump_to_log_succeed : true;
}
bool CrashReportExceptionHandler::WriteMinidumpToLog(
ProcessSnapshotLinux* process_snapshot,
ProcessSnapshotSanitized* sanitized_snapshot) {
ProcessSnapshot* snapshot =
sanitized_snapshot ? implicit_cast<ProcessSnapshot*>(sanitized_snapshot)
: implicit_cast<ProcessSnapshot*>(process_snapshot);
MinidumpFileWriter minidump;
minidump.InitializeFromSnapshot(snapshot);
AddUserExtensionStreams(user_stream_data_sources_, snapshot, &minidump);
OutputStreamFileWriter writer(std::make_unique<ZlibOutputStream>(
ZlibOutputStream::Mode::kCompress,
std::make_unique<Base94OutputStream>(
Base94OutputStream::Mode::kEncode,
std::make_unique<LogOutputStream>())));
if (!minidump.WriteMinidump(&writer, false /* allow_seek */)) {
LOG(ERROR) << "WriteMinidump failed";
return false;
}
return writer.Flush();
} }
} // namespace crashpad } // namespace crashpad

View File

@ -26,9 +26,13 @@
#include "util/linux/exception_handler_protocol.h" #include "util/linux/exception_handler_protocol.h"
#include "util/linux/ptrace_connection.h" #include "util/linux/ptrace_connection.h"
#include "util/misc/address_types.h" #include "util/misc/address_types.h"
#include "util/misc/uuid.h"
namespace crashpad { namespace crashpad {
class ProcessSnapshotLinux;
class ProcessSnapshotSanitized;
//! \brief An exception handler that writes crash reports for exceptions //! \brief An exception handler that writes crash reports for exceptions
//! to a CrashReportDatabase. //! to a CrashReportDatabase.
class CrashReportExceptionHandler : public ExceptionHandlerServer::Delegate { class CrashReportExceptionHandler : public ExceptionHandlerServer::Delegate {
@ -49,6 +53,10 @@ class CrashReportExceptionHandler : public ExceptionHandlerServer::Delegate {
//! To interoperate with Breakpad servers, the recommended practice is to //! To interoperate with Breakpad servers, the recommended practice is to
//! specify values for the `"prod"` and `"ver"` keys as process //! specify values for the `"prod"` and `"ver"` keys as process
//! annotations. //! 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 //! \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 //! crash reports. For each crash report that is written, the data sources
//! are called in turn. These data sources may contribute additional //! are called in turn. These data sources may contribute additional
@ -57,26 +65,49 @@ class CrashReportExceptionHandler : public ExceptionHandlerServer::Delegate {
CrashReportDatabase* database, CrashReportDatabase* database,
CrashReportUploadThread* upload_thread, CrashReportUploadThread* upload_thread,
const std::map<std::string, std::string>* process_annotations, const std::map<std::string, std::string>* process_annotations,
bool write_minidump_to_database,
bool write_minidump_to_log,
const UserStreamDataSources* user_stream_data_sources); const UserStreamDataSources* user_stream_data_sources);
~CrashReportExceptionHandler(); ~CrashReportExceptionHandler() override;
// ExceptionHandlerServer::Delegate: // ExceptionHandlerServer::Delegate:
bool HandleException(pid_t client_process_id, 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, bool HandleExceptionWithBroker(
const ClientInformation& info, pid_t client_process_id,
int broker_sock) override; uid_t client_uid,
const ExceptionHandlerProtocol::ClientInformation& info,
int broker_sock,
UUID* local_report_id = nullptr) override;
private: private:
bool HandleExceptionWithConnection(PtraceConnection* connection, bool HandleExceptionWithConnection(
const ClientInformation& info); 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 CrashReportDatabase* database_; // weak
CrashReportUploadThread* upload_thread_; // weak CrashReportUploadThread* upload_thread_; // weak
const std::map<std::string, std::string>* process_annotations_; // weak const std::map<std::string, std::string>* process_annotations_; // weak
bool write_minidump_to_database_;
bool write_minidump_to_log_;
const UserStreamDataSources* user_stream_data_sources_; // weak const UserStreamDataSources* user_stream_data_sources_; // weak
DISALLOW_COPY_AND_ASSIGN(CrashReportExceptionHandler); DISALLOW_COPY_AND_ASSIGN(CrashReportExceptionHandler);

View File

@ -0,0 +1,286 @@
// Copyright 2019 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "handler/linux/cros_crash_report_exception_handler.h"
#include <vector>
#include "base/logging.h"
#include "client/settings.h"
#include "handler/linux/capture_snapshot.h"
#include "handler/minidump_to_upload_parameters.h"
#include "minidump/minidump_file_writer.h"
#include "snapshot/linux/process_snapshot_linux.h"
#include "snapshot/minidump/process_snapshot_minidump.h"
#include "snapshot/sanitized/process_snapshot_sanitized.h"
#include "util/file/file_writer.h"
#include "util/linux/direct_ptrace_connection.h"
#include "util/linux/ptrace_client.h"
#include "util/misc/metrics.h"
#include "util/misc/uuid.h"
#include "util/posix/double_fork_and_exec.h"
namespace crashpad {
namespace {
// Returns the process name for a pid.
const std::string GetProcessNameFromPid(pid_t pid) {
// Symlink to process binary is at /proc/###/exe.
std::string link_path = "/proc/" + std::to_string(pid) + "/exe";
constexpr int kMaxSize = 4096;
std::unique_ptr<char[]> buf(new char[kMaxSize]);
ssize_t size = readlink(link_path.c_str(), buf.get(), kMaxSize);
std::string result;
if (size < 0) {
PLOG(ERROR) << "Failed to readlink " << link_path;
} else {
result.assign(buf.get(), size);
size_t last_slash_pos = result.rfind('/');
if (last_slash_pos != std::string::npos) {
result = result.substr(last_slash_pos + 1);
}
}
return result;
}
bool WriteAnnotationsAndMinidump(
const std::map<std::string, std::string>& parameters,
MinidumpFileWriter& minidump,
FileWriter& file_writer) {
for (const auto& kv : parameters) {
if (kv.first.find(':') != std::string::npos) {
LOG(ERROR) << "Annotation key cannot have ':' in it " << kv.first;
return false;
}
if (!file_writer.Write(kv.first.c_str(), strlen(kv.first.c_str()))) {
return false;
}
if (!file_writer.Write(":", 1)) {
return false;
}
size_t value_size = strlen(kv.second.c_str());
std::string value_size_str = std::to_string(value_size);
if (!file_writer.Write(value_size_str.c_str(), value_size_str.size())) {
return false;
}
if (!file_writer.Write(":", 1)) {
return false;
}
if (!file_writer.Write(kv.second.c_str(), strlen(kv.second.c_str()))) {
return false;
}
}
static constexpr char kMinidumpName[] =
"upload_file_minidump\"; filename=\"dump\":";
if (!file_writer.Write(kMinidumpName, sizeof(kMinidumpName) - 1)) {
return false;
}
crashpad::FileOffset dump_size_start_offset = file_writer.Seek(0, SEEK_CUR);
if (dump_size_start_offset < 0) {
LOG(ERROR) << "Failed to get minidump size start offset";
return false;
}
static constexpr char kMinidumpLengthFilling[] = "00000000000000000000:";
if (!file_writer.Write(kMinidumpLengthFilling,
sizeof(kMinidumpLengthFilling) - 1)) {
return false;
}
crashpad::FileOffset dump_start_offset = file_writer.Seek(0, SEEK_CUR);
if (dump_start_offset < 0) {
LOG(ERROR) << "Failed to get minidump start offset";
return false;
}
if (!minidump.WriteEverything(&file_writer)) {
return false;
}
crashpad::FileOffset dump_end_offset = file_writer.Seek(0, SEEK_CUR);
if (dump_end_offset < 0) {
LOG(ERROR) << "Failed to get minidump end offset";
return false;
}
size_t dump_data_size = dump_end_offset - dump_start_offset;
std::string dump_data_size_str = std::to_string(dump_data_size);
file_writer.Seek(dump_size_start_offset + strlen(kMinidumpLengthFilling) - 1 -
dump_data_size_str.size(),
SEEK_SET);
if (!file_writer.Write(dump_data_size_str.c_str(),
dump_data_size_str.size())) {
return false;
}
return true;
}
} // namespace
CrosCrashReportExceptionHandler::CrosCrashReportExceptionHandler(
CrashReportDatabase* database,
const std::map<std::string, std::string>* process_annotations,
const UserStreamDataSources* user_stream_data_sources)
: database_(database),
process_annotations_(process_annotations),
user_stream_data_sources_(user_stream_data_sources),
always_allow_feedback_(false) {}
CrosCrashReportExceptionHandler::~CrosCrashReportExceptionHandler() = default;
bool CrosCrashReportExceptionHandler::HandleException(
pid_t client_process_id,
uid_t client_uid,
const ExceptionHandlerProtocol::ClientInformation& info,
VMAddress requesting_thread_stack_address,
pid_t* requesting_thread_id,
UUID* local_report_id) {
Metrics::ExceptionEncountered();
DirectPtraceConnection connection;
if (!connection.Initialize(client_process_id)) {
Metrics::ExceptionCaptureResult(
Metrics::CaptureResult::kDirectPtraceFailed);
return false;
}
return HandleExceptionWithConnection(&connection,
info,
client_uid,
requesting_thread_stack_address,
requesting_thread_id,
local_report_id);
}
bool CrosCrashReportExceptionHandler::HandleExceptionWithBroker(
pid_t client_process_id,
uid_t client_uid,
const ExceptionHandlerProtocol::ClientInformation& info,
int broker_sock,
UUID* local_report_id) {
Metrics::ExceptionEncountered();
PtraceClient client;
if (!client.Initialize(broker_sock, client_process_id)) {
Metrics::ExceptionCaptureResult(
Metrics::CaptureResult::kBrokeredPtraceFailed);
return false;
}
return HandleExceptionWithConnection(
&client, info, client_uid, 0, nullptr, local_report_id);
}
bool CrosCrashReportExceptionHandler::HandleExceptionWithConnection(
PtraceConnection* connection,
const ExceptionHandlerProtocol::ClientInformation& info,
uid_t client_uid,
VMAddress requesting_thread_stack_address,
pid_t* requesting_thread_id,
UUID* local_report_id) {
std::unique_ptr<ProcessSnapshotLinux> process_snapshot;
std::unique_ptr<ProcessSnapshotSanitized> sanitized_snapshot;
if (!CaptureSnapshot(connection,
info,
*process_annotations_,
client_uid,
requesting_thread_stack_address,
requesting_thread_id,
&process_snapshot,
&sanitized_snapshot)) {
return false;
}
UUID client_id;
Settings* const settings = database_->GetSettings();
if (settings) {
// If GetSettings() or GetClientID() fails, something else will log a
// message and client_id will be left at its default value, all zeroes,
// which is appropriate.
settings->GetClientID(&client_id);
}
process_snapshot->SetClientID(client_id);
UUID uuid;
uuid.InitializeWithNew();
process_snapshot->SetReportID(uuid);
ProcessSnapshot* snapshot =
sanitized_snapshot
? implicit_cast<ProcessSnapshot*>(sanitized_snapshot.get())
: implicit_cast<ProcessSnapshot*>(process_snapshot.get());
MinidumpFileWriter minidump;
minidump.InitializeFromSnapshot(snapshot);
AddUserExtensionStreams(user_stream_data_sources_, snapshot, &minidump);
FileWriter file_writer;
if (!file_writer.OpenMemfd(base::FilePath("minidump"))) {
Metrics::ExceptionCaptureResult(Metrics::CaptureResult::kOpenMemfdFailed);
return false;
}
std::map<std::string, std::string> parameters =
BreakpadHTTPFormParametersFromMinidump(snapshot);
// Used to differentiate between breakpad and crashpad while the switch is
// ramping up.
parameters.emplace("crash_library", "crashpad");
if (!WriteAnnotationsAndMinidump(parameters, minidump, file_writer)) {
Metrics::ExceptionCaptureResult(
Metrics::CaptureResult::kMinidumpWriteFailed);
return false;
}
// CrOS uses crash_reporter instead of Crashpad to report crashes.
// crash_reporter needs to know the pid and uid of the crashing process.
std::vector<std::string> argv({"/sbin/crash_reporter"});
argv.push_back("--chrome_memfd=" + std::to_string(file_writer.fd()));
argv.push_back("--pid=" + std::to_string(*requesting_thread_id));
argv.push_back("--uid=" + std::to_string(client_uid));
std::string process_name = GetProcessNameFromPid(*requesting_thread_id);
argv.push_back("--exe=" + (process_name.empty() ? "chrome" : process_name));
if (info.crash_loop_before_time != 0) {
argv.push_back("--crash_loop_before=" +
std::to_string(info.crash_loop_before_time));
}
if (!dump_dir_.empty()) {
argv.push_back("--chrome_dump_dir=" + dump_dir_.value());
}
if (always_allow_feedback_) {
argv.push_back("--always_allow_feedback");
}
if (!DoubleForkAndExec(argv,
nullptr /* envp */,
file_writer.fd() /* preserve_fd */,
false /* use_path */,
nullptr /* child_function */)) {
LOG(ERROR) << "DoubleForkAndExec failed";
Metrics::ExceptionCaptureResult(
Metrics::CaptureResult::kFinishedWritingCrashReportFailed);
return false;
}
if (local_report_id != nullptr) {
*local_report_id = uuid;
}
LOG(INFO) << "Successfully wrote report " << uuid.ToString();
Metrics::ExceptionCaptureResult(Metrics::CaptureResult::kSuccess);
return true;
}
} // namespace crashpad

View File

@ -0,0 +1,101 @@
// Copyright 2019 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef CRASHPAD_HANDLER_LINUX_CROS_CRASH_REPORT_EXCEPTION_HANDLER_H_
#define CRASHPAD_HANDLER_LINUX_CROS_CRASH_REPORT_EXCEPTION_HANDLER_H_
#include <map>
#include <string>
#include "base/macros.h"
#include "client/crash_report_database.h"
#include "handler/linux/exception_handler_server.h"
#include "handler/user_stream_data_source.h"
#include "util/linux/exception_handler_protocol.h"
#include "util/linux/ptrace_connection.h"
#include "util/misc/address_types.h"
#include "util/misc/uuid.h"
namespace crashpad {
//! \brief An exception handler that writes crash reports to the ChromeOS
//! crash_reporter.
class CrosCrashReportExceptionHandler
: public ExceptionHandlerServer::Delegate {
public:
//! \brief Creates a new object that will pass reports to
//! `/sbin/crash_reporter`.
//!
//! \param[in] database The database that supplies settings for this client.
//! This object does not write its reports to this database.
//! \param[in] process_annotations A map of annotations to insert as
//! process-level annotations into each crash report that is written. Do
//! not confuse this with module-level annotations, which are under the
//! control of the crashing process, and are used to implement Chromes
//! “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 its able to establish its own annotations.
//! To interoperate with Breakpad servers, the recommended practice is to
//! specify values for the `"prod"` and `"ver"` keys as process
//! annotations.
//! \param[in] user_stream_data_sources Data sources to be used to extend
//! crash reports. For each crash report that is written, the data sources
//! are called in turn. These data sources may contribute additional
//! minidump streams. `nullptr` if not required.
CrosCrashReportExceptionHandler(
CrashReportDatabase* database,
const std::map<std::string, std::string>* process_annotations,
const UserStreamDataSources* user_stream_data_sources);
~CrosCrashReportExceptionHandler() override;
// ExceptionHandlerServer::Delegate:
bool HandleException(pid_t client_process_id,
uid_t client_uid,
const ExceptionHandlerProtocol::ClientInformation& info,
VMAddress requesting_thread_stack_address = 0,
pid_t* requesting_thread_id = nullptr,
UUID* local_report_id = nullptr) override;
bool HandleExceptionWithBroker(
pid_t client_process_id,
uid_t client_uid,
const ExceptionHandlerProtocol::ClientInformation& info,
int broker_sock,
UUID* local_report_id = nullptr) override;
void SetDumpDir(const base::FilePath& dump_dir) { dump_dir_ = dump_dir; }
void SetAlwaysAllowFeedback() { always_allow_feedback_ = true; }
private:
bool HandleExceptionWithConnection(
PtraceConnection* connection,
const ExceptionHandlerProtocol::ClientInformation& info,
uid_t client_uid,
VMAddress requesting_thread_stack_address,
pid_t* requesting_thread_id,
UUID* local_report_id = nullptr);
CrashReportDatabase* database_; // weak
const std::map<std::string, std::string>* process_annotations_; // weak
const UserStreamDataSources* user_stream_data_sources_; // weak
base::FilePath dump_dir_;
bool always_allow_feedback_;
DISALLOW_COPY_AND_ASSIGN(CrosCrashReportExceptionHandler);
};
} // namespace crashpad
#endif // CRASHPAD_HANDLER_LINUX_CROS_CRASH_REPORT_EXCEPTION_HANDLER_H_

View File

@ -15,10 +15,11 @@
#include "handler/linux/exception_handler_server.h" #include "handler/linux/exception_handler_server.h"
#include <errno.h> #include <errno.h>
#include <sys/capability.h> #include <linux/capability.h>
#include <sys/epoll.h> #include <sys/epoll.h>
#include <sys/eventfd.h> #include <sys/eventfd.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/types.h> #include <sys/types.h>
#include <unistd.h> #include <unistd.h>
@ -31,6 +32,8 @@
#include "build/build_config.h" #include "build/build_config.h"
#include "util/file/file_io.h" #include "util/file/file_io.h"
#include "util/file/filesystem.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" #include "util/misc/as_underlying_type.h"
namespace crashpad { namespace crashpad {
@ -90,54 +93,96 @@ PtraceScope GetPtraceScope() {
} }
bool HaveCapSysPtrace() { bool HaveCapSysPtrace() {
struct __user_cap_header_struct cap_header = {}; __user_cap_header_struct cap_header;
struct __user_cap_data_struct cap_data = {};
cap_header.pid = getpid(); 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"; PLOG(ERROR) << "capget";
return false; LOG_IF(ERROR, errno == EINVAL) << "cap_header.version " << std::hex
}
if (cap_header.version != _LINUX_CAPABILITY_VERSION_3) {
LOG(ERROR) << "Unexpected capability version " << std::hex
<< cap_header.version; << cap_header.version;
return false; 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) { bool SendMessageToClient(
ServerToClientMessage message = {}; int client_sock,
ExceptionHandlerProtocol::ServerToClientMessage::Type type) {
ExceptionHandlerProtocol::ServerToClientMessage message = {};
message.type = type; message.type = type;
if (type == ServerToClientMessage::kTypeSetPtracer) { if (type ==
ExceptionHandlerProtocol::ServerToClientMessage::kTypeSetPtracer) {
message.pid = getpid(); message.pid = getpid();
} }
return LoggingWriteFile(client_sock, &message, sizeof(message)); return LoggingWriteFile(client_sock, &message, sizeof(message));
} }
int tgkill(pid_t pid, pid_t tid, int signo) {
return syscall(SYS_tgkill, pid, tid, signo);
}
void SendSIGCONT(pid_t pid, pid_t tid) {
if (tid > 0) {
if (tgkill(pid, tid, ExceptionHandlerProtocol::kDumpDoneSignal) != 0) {
PLOG(ERROR) << "tgkill";
}
return;
}
std::vector<pid_t> threads;
if (!ReadThreadIDs(pid, &threads)) {
return;
}
for (const auto& thread : threads) {
if (tgkill(pid, thread, ExceptionHandlerProtocol::kDumpDoneSignal) != 0) {
PLOG(ERROR) << "tgkill";
}
}
}
bool SendCredentials(int client_sock) {
ExceptionHandlerProtocol::ServerToClientMessage message = {};
message.type =
ExceptionHandlerProtocol::ServerToClientMessage::kTypeCredentials;
return UnixCredentialSocket::SendMsg(
client_sock, &message, sizeof(message)) == 0;
}
class PtraceStrategyDeciderImpl : public PtraceStrategyDecider { class PtraceStrategyDeciderImpl : public PtraceStrategyDecider {
public: public:
PtraceStrategyDeciderImpl() : PtraceStrategyDecider() {} PtraceStrategyDeciderImpl() : PtraceStrategyDecider() {}
~PtraceStrategyDeciderImpl() = default; ~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()) { switch (GetPtraceScope()) {
case PtraceScope::kClassic: case PtraceScope::kClassic:
if (getuid() == client_credentials.uid) { if (getuid() == client_credentials.uid || HaveCapSysPtrace()) {
return Strategy::kDirectPtrace; return Strategy::kDirectPtrace;
} }
return TryForkingBroker(sock); return multiple_clients ? Strategy::kNoPtrace : TryForkingBroker(sock);
case PtraceScope::kRestricted: case PtraceScope::kRestricted:
if (multiple_clients) {
return Strategy::kDirectPtrace;
}
if (!SendMessageToClient(sock, if (!SendMessageToClient(sock,
ExceptionHandlerProtocol::
ServerToClientMessage::kTypeSetPtracer)) { ServerToClientMessage::kTypeSetPtracer)) {
return Strategy::kError; return Strategy::kError;
} }
Errno status; ExceptionHandlerProtocol::Errno status;
if (!LoggingReadFileExactly(sock, &status, sizeof(status))) { if (!LoggingReadFileExactly(sock, &status, sizeof(status))) {
return Strategy::kError; return Strategy::kError;
} }
@ -169,12 +214,13 @@ class PtraceStrategyDeciderImpl : public PtraceStrategyDecider {
private: private:
static Strategy TryForkingBroker(int client_sock) { static Strategy TryForkingBroker(int client_sock) {
if (!SendMessageToClient(client_sock, if (!SendMessageToClient(
ServerToClientMessage::kTypeForkBroker)) { client_sock,
ExceptionHandlerProtocol::ServerToClientMessage::kTypeForkBroker)) {
return Strategy::kError; return Strategy::kError;
} }
Errno status; ExceptionHandlerProtocol::Errno status;
if (!LoggingReadFileExactly(client_sock, &status, sizeof(status))) { if (!LoggingReadFileExactly(client_sock, &status, sizeof(status))) {
return Strategy::kError; return Strategy::kError;
} }
@ -190,12 +236,6 @@ class PtraceStrategyDeciderImpl : public PtraceStrategyDecider {
} // namespace } // namespace
struct ExceptionHandlerServer::Event {
enum class Type { kShutdown, kClientMessage } type;
ScopedFileHandle fd;
};
ExceptionHandlerServer::ExceptionHandlerServer() ExceptionHandlerServer::ExceptionHandlerServer()
: clients_(), : clients_(),
shutdown_event_(), shutdown_event_(),
@ -211,7 +251,8 @@ void ExceptionHandlerServer::SetPtraceStrategyDecider(
strategy_decider_ = std::move(decider); strategy_decider_ = std::move(decider);
} }
bool ExceptionHandlerServer::InitializeWithClient(ScopedFileHandle sock) { bool ExceptionHandlerServer::InitializeWithClient(ScopedFileHandle sock,
bool multiple_clients) {
INITIALIZATION_STATE_SET_INITIALIZING(initialized_); INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
pollfd_.reset(epoll_create1(EPOLL_CLOEXEC)); pollfd_.reset(epoll_create1(EPOLL_CLOEXEC));
@ -239,7 +280,9 @@ bool ExceptionHandlerServer::InitializeWithClient(ScopedFileHandle sock) {
return false; return false;
} }
if (!InstallClientSocket(std::move(sock))) { if (!InstallClientSocket(std::move(sock),
multiple_clients ? Event::Type::kSharedSocketMessage
: Event::Type::kClientMessage)) {
return false; return false;
} }
@ -281,8 +324,8 @@ void ExceptionHandlerServer::Stop() {
} }
void ExceptionHandlerServer::HandleEvent(Event* event, uint32_t event_type) { void ExceptionHandlerServer::HandleEvent(Event* event, uint32_t event_type) {
DCHECK_EQ(AsUnderlyingType(event->type), DCHECK_NE(AsUnderlyingType(event->type),
AsUnderlyingType(Event::Type::kClientMessage)); AsUnderlyingType(Event::Type::kShutdown));
if (event_type & EPOLLERR) { if (event_type & EPOLLERR) {
LogSocketError(event->fd.get()); LogSocketError(event->fd.get());
@ -306,16 +349,30 @@ void ExceptionHandlerServer::HandleEvent(Event* event, uint32_t event_type) {
return; return;
} }
bool ExceptionHandlerServer::InstallClientSocket(ScopedFileHandle socket) { bool ExceptionHandlerServer::InstallClientSocket(ScopedFileHandle socket,
int optval = 1; 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); socklen_t optlen = sizeof(optval);
if (setsockopt(socket.get(), SOL_SOCKET, SO_PASSCRED, &optval, optlen) != 0) { 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"; PLOG(ERROR) << "setsockopt";
return false; return false;
} }
}
auto event = std::make_unique<Event>(); auto event = std::make_unique<Event>();
event->type = Event::Type::kClientMessage; event->type = type;
event->fd.reset(socket.release()); event->fd.reset(socket.release());
Event* eventp = event.get(); Event* eventp = event.get();
@ -355,53 +412,24 @@ bool ExceptionHandlerServer::UninstallClientSocket(Event* event) {
} }
bool ExceptionHandlerServer::ReceiveClientMessage(Event* event) { bool ExceptionHandlerServer::ReceiveClientMessage(Event* event) {
ClientToServerMessage message; ExceptionHandlerProtocol::ClientToServerMessage message;
iovec iov; ucred creds;
iov.iov_base = &message; if (!UnixCredentialSocket::RecvMsg(
iov.iov_len = sizeof(message); event->fd.get(), &message, sizeof(message), &creds)) {
msghdr msg;
msg.msg_name = nullptr;
msg.msg_namelen = 0;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
char cmsg_buf[CMSG_SPACE(sizeof(ucred))];
msg.msg_control = cmsg_buf;
msg.msg_controllen = sizeof(cmsg_buf);
msg.msg_flags = 0;
int res = HANDLE_EINTR(recvmsg(event->fd.get(), &msg, 0));
if (res < 0) {
PLOG(ERROR) << "recvmsg";
return false;
}
if (res == 0) {
// The client had an orderly shutdown.
return false; return false;
} }
if (msg.msg_name != nullptr || msg.msg_namelen != 0) { switch (message.type) {
LOG(ERROR) << "unexpected msg name"; case ExceptionHandlerProtocol::ClientToServerMessage::kTypeCheckCredentials:
return false; return SendCredentials(event->fd.get());
}
if (msg.msg_iovlen != 1) { case ExceptionHandlerProtocol::ClientToServerMessage::kTypeCrashDumpRequest:
LOG(ERROR) << "unexpected iovlen";
return false;
}
if (msg.msg_iov[0].iov_len != sizeof(ClientToServerMessage)) {
LOG(ERROR) << "unexpected message size " << msg.msg_iov[0].iov_len;
return false;
}
auto client_msg =
reinterpret_cast<ClientToServerMessage*>(msg.msg_iov[0].iov_base);
switch (client_msg->type) {
case ClientToServerMessage::kCrashDumpRequest:
return HandleCrashDumpRequest( 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); DCHECK(false);
@ -410,53 +438,56 @@ bool ExceptionHandlerServer::ReceiveClientMessage(Event* event) {
} }
bool ExceptionHandlerServer::HandleCrashDumpRequest( bool ExceptionHandlerServer::HandleCrashDumpRequest(
const msghdr& msg, const ucred& creds,
const ClientInformation& client_info, const ExceptionHandlerProtocol::ClientInformation& client_info,
int client_sock) { VMAddress requesting_thread_stack_address,
cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); int client_sock,
if (cmsg == nullptr) { bool multiple_clients) {
LOG(ERROR) << "missing credentials"; pid_t client_process_id = creds.pid;
return false; pid_t requesting_thread_id = -1;
} uid_t client_uid = creds.uid;
if (cmsg->cmsg_level != SOL_SOCKET) { switch (
LOG(ERROR) << "unexpected cmsg_level " << cmsg->cmsg_level; strategy_decider_->ChooseStrategy(client_sock, multiple_clients, creds)) {
return false;
}
if (cmsg->cmsg_type != SCM_CREDENTIALS) {
LOG(ERROR) << "unexpected cmsg_type " << cmsg->cmsg_type;
return false;
}
if (cmsg->cmsg_len != CMSG_LEN(sizeof(ucred))) {
LOG(ERROR) << "unexpected cmsg_len " << cmsg->cmsg_len;
return false;
}
ucred* client_credentials = reinterpret_cast<ucred*>(CMSG_DATA(cmsg));
pid_t client_process_id = client_credentials->pid;
switch (strategy_decider_->ChooseStrategy(client_sock, *client_credentials)) {
case PtraceStrategyDecider::Strategy::kError: case PtraceStrategyDecider::Strategy::kError:
if (multiple_clients) {
SendSIGCONT(client_process_id, requesting_thread_id);
}
return false; return false;
case PtraceStrategyDecider::Strategy::kNoPtrace: case PtraceStrategyDecider::Strategy::kNoPtrace:
return SendMessageToClient(client_sock, if (multiple_clients) {
ServerToClientMessage::kTypeCrashDumpFailed); SendSIGCONT(client_process_id, requesting_thread_id);
return true;
}
return SendMessageToClient(
client_sock,
ExceptionHandlerProtocol::ServerToClientMessage::
kTypeCrashDumpFailed);
case PtraceStrategyDecider::Strategy::kDirectPtrace: case PtraceStrategyDecider::Strategy::kDirectPtrace: {
delegate_->HandleException(client_process_id, client_info); delegate_->HandleException(client_process_id,
break; client_uid,
client_info,
case PtraceStrategyDecider::Strategy::kUseBroker: requesting_thread_stack_address,
delegate_->HandleExceptionWithBroker( &requesting_thread_id);
client_process_id, client_info, client_sock); if (multiple_clients) {
SendSIGCONT(client_process_id, requesting_thread_id);
return true;
}
break; break;
} }
return SendMessageToClient(client_sock, case PtraceStrategyDecider::Strategy::kUseBroker:
ServerToClientMessage::kTypeCrashDumpComplete); DCHECK(!multiple_clients);
delegate_->HandleExceptionWithBroker(
client_process_id, client_uid, client_info, client_sock);
break;
}
return SendMessageToClient(
client_sock,
ExceptionHandlerProtocol::ServerToClientMessage::kTypeCrashDumpComplete);
} }
} // namespace crashpad } // namespace crashpad

View File

@ -18,6 +18,7 @@
#include <stdint.h> #include <stdint.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <atomic>
#include <memory> #include <memory>
#include <unordered_map> #include <unordered_map>
@ -26,6 +27,7 @@
#include "util/linux/exception_handler_protocol.h" #include "util/linux/exception_handler_protocol.h"
#include "util/misc/address_types.h" #include "util/misc/address_types.h"
#include "util/misc/initialization_state_dcheck.h" #include "util/misc/initialization_state_dcheck.h"
#include "util/misc/uuid.h"
namespace crashpad { namespace crashpad {
@ -53,9 +55,12 @@ class PtraceStrategyDecider {
//! \brief Chooses an appropriate `ptrace` strategy. //! \brief Chooses an appropriate `ptrace` strategy.
//! //!
//! \param[in] sock A socket conncted to a ExceptionHandlerClient. //! \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. //! \param[in] client_credentials The credentials for the connected client.
//! \return the chosen #Strategy. //! \return the chosen #Strategy.
virtual Strategy ChooseStrategy(int sock, virtual Strategy ChooseStrategy(int sock,
bool multiple_clients,
const ucred& client_credentials) = 0; const ucred& client_credentials) = 0;
protected: protected:
@ -71,24 +76,43 @@ class ExceptionHandlerServer {
//! \brief Called on receipt of a crash dump request from a client. //! \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_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] 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. //! \return `true` on success. `false` on failure with a message logged.
virtual bool HandleException(pid_t client_process_id, virtual bool HandleException(
const ClientInformation& info) = 0; 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 //! \brief Called on the receipt of a crash dump request from a client for a
//! crash that should be mediated by a PtraceBroker. //! crash that should be mediated by a PtraceBroker.
//! //!
//! \param[in] client_process_id The process ID of the crashing client. //! \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] info Information on the client.
//! \param[in] broker_sock A socket connected to the PtraceBroker. //! \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. //! \return `true` on success. `false` on failure with a message logged.
virtual bool HandleExceptionWithBroker(pid_t client_process_id, virtual bool HandleExceptionWithBroker(
const ClientInformation& info, pid_t client_process_id,
int broker_sock) = 0; uid_t client_uid,
const ExceptionHandlerProtocol::ClientInformation& info,
int broker_sock,
UUID* local_report_id = nullptr) = 0;
protected: virtual ~Delegate() {}
~Delegate() {}
}; };
ExceptionHandlerServer(); ExceptionHandlerServer();
@ -105,8 +129,11 @@ class ExceptionHandlerServer {
//! This method must be successfully called before Run(). //! This method must be successfully called before Run().
//! //!
//! \param[in] sock A socket on which to receive client requests. //! \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. //! \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. //! \brief Runs the exception-handling server.
//! //!
@ -126,22 +153,39 @@ class ExceptionHandlerServer {
void Stop(); void Stop();
private: 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); void HandleEvent(Event* event, uint32_t event_type);
bool InstallClientSocket(ScopedFileHandle socket); bool InstallClientSocket(ScopedFileHandle socket, Event::Type type);
bool UninstallClientSocket(Event* event); bool UninstallClientSocket(Event* event);
bool ReceiveClientMessage(Event* event); bool ReceiveClientMessage(Event* event);
bool HandleCrashDumpRequest(const msghdr& msg, bool HandleCrashDumpRequest(
const ClientInformation& client_info, const ucred& creds,
int client_sock); const ExceptionHandlerProtocol::ClientInformation& client_info,
VMAddress requesting_thread_stack_address,
int client_sock,
bool multiple_clients);
std::unordered_map<int, std::unique_ptr<Event>> clients_; std::unordered_map<int, std::unique_ptr<Event>> clients_;
std::unique_ptr<Event> shutdown_event_; std::unique_ptr<Event> shutdown_event_;
std::unique_ptr<PtraceStrategyDecider> strategy_decider_; std::unique_ptr<PtraceStrategyDecider> strategy_decider_;
Delegate* delegate_; Delegate* delegate_;
ScopedFileHandle pollfd_; ScopedFileHandle pollfd_;
bool keep_running_; std::atomic<bool> keep_running_;
InitializationStateDcheck initialized_; InitializationStateDcheck initialized_;
DISALLOW_COPY_AND_ASSIGN(ExceptionHandlerServer); DISALLOW_COPY_AND_ASSIGN(ExceptionHandlerServer);

View File

@ -18,16 +18,23 @@
#include <unistd.h> #include <unistd.h>
#include "base/logging.h" #include "base/logging.h"
#include "build/build_config.h"
#include "gtest/gtest.h" #include "gtest/gtest.h"
#include "snapshot/linux/process_snapshot_linux.h"
#include "test/errors.h" #include "test/errors.h"
#include "test/multiprocess.h" #include "test/multiprocess.h"
#include "util/linux/direct_ptrace_connection.h" #include "util/linux/direct_ptrace_connection.h"
#include "util/linux/exception_handler_client.h" #include "util/linux/exception_handler_client.h"
#include "util/linux/ptrace_client.h" #include "util/linux/ptrace_client.h"
#include "util/linux/scoped_pr_set_ptracer.h" #include "util/linux/scoped_pr_set_ptracer.h"
#include "util/misc/uuid.h"
#include "util/synchronization/semaphore.h" #include "util/synchronization/semaphore.h"
#include "util/thread/thread.h" #include "util/thread/thread.h"
#if defined(OS_ANDROID)
#include <android/api-level.h>
#endif
namespace crashpad { namespace crashpad {
namespace test { namespace test {
namespace { namespace {
@ -101,7 +108,11 @@ class TestDelegate : public ExceptionHandlerServer::Delegate {
} }
bool HandleException(pid_t client_process_id, 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; DirectPtraceConnection connection;
bool connected = connection.Initialize(client_process_id); bool connected = connection.Initialize(client_process_id);
EXPECT_TRUE(connected); EXPECT_TRUE(connected);
@ -109,12 +120,32 @@ class TestDelegate : public ExceptionHandlerServer::Delegate {
last_exception_address_ = info.exception_information_address; last_exception_address_ = info.exception_information_address;
last_client_ = client_process_id; last_client_ = client_process_id;
sem_.Signal(); sem_.Signal();
return connected; if (!connected) {
return false;
} }
bool HandleExceptionWithBroker(pid_t client_process_id, if (requesting_thread_id) {
const ClientInformation& info, if (requesting_thread_stack_address) {
int broker_sock) override { 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,
uid_t client_uid,
const ExceptionHandlerProtocol::ClientInformation& info,
int broker_sock,
UUID* local_report_id = nullptr) override {
PtraceClient client; PtraceClient client;
bool connected = client.Initialize(broker_sock, client_process_id); bool connected = client.Initialize(broker_sock, client_process_id);
EXPECT_TRUE(connected); EXPECT_TRUE(connected);
@ -140,12 +171,15 @@ class MockPtraceStrategyDecider : public PtraceStrategyDecider {
~MockPtraceStrategyDecider() {} ~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) { if (strategy_ == Strategy::kUseBroker) {
ServerToClientMessage message = {}; ExceptionHandlerProtocol::ServerToClientMessage message = {};
message.type = ServerToClientMessage::kTypeForkBroker; message.type =
ExceptionHandlerProtocol::ServerToClientMessage::kTypeForkBroker;
Errno status; ExceptionHandlerProtocol::Errno status;
bool result = LoggingWriteFile(sock, &message, sizeof(message)) && bool result = LoggingWriteFile(sock, &message, sizeof(message)) &&
LoggingReadFileExactly(sock, &status, sizeof(status)); LoggingReadFileExactly(sock, &status, sizeof(status));
EXPECT_TRUE(result); EXPECT_TRUE(result);
@ -169,13 +203,14 @@ class MockPtraceStrategyDecider : public PtraceStrategyDecider {
DISALLOW_COPY_AND_ASSIGN(MockPtraceStrategyDecider); DISALLOW_COPY_AND_ASSIGN(MockPtraceStrategyDecider);
}; };
class ExceptionHandlerServerTest : public testing::Test { class ExceptionHandlerServerTest : public testing::TestWithParam<bool> {
public: public:
ExceptionHandlerServerTest() ExceptionHandlerServerTest()
: server_(), : server_(),
delegate_(), delegate_(),
server_thread_(&server_, &delegate_), server_thread_(&server_, &delegate_),
sock_to_handler_() {} sock_to_handler_(),
use_multi_client_socket_(GetParam()) {}
~ExceptionHandlerServerTest() = default; ~ExceptionHandlerServerTest() = default;
@ -197,7 +232,7 @@ class ExceptionHandlerServerTest : public testing::Test {
~CrashDumpTest() = default; ~CrashDumpTest() = default;
void MultiprocessParent() override { void MultiprocessParent() override {
ClientInformation info; ExceptionHandlerProtocol::ClientInformation info;
ASSERT_TRUE( ASSERT_TRUE(
LoggingReadFileExactly(ReadPipeHandle(), &info, sizeof(info))); LoggingReadFileExactly(ReadPipeHandle(), &info, sizeof(info)));
@ -216,9 +251,8 @@ class ExceptionHandlerServerTest : public testing::Test {
void MultiprocessChild() override { void MultiprocessChild() override {
ASSERT_EQ(close(server_test_->sock_to_client_), 0); ASSERT_EQ(close(server_test_->sock_to_client_), 0);
ClientInformation info; ExceptionHandlerProtocol::ClientInformation info;
info.exception_information_address = 42; info.exception_information_address = 42;
ASSERT_TRUE(LoggingWriteFile(WritePipeHandle(), &info, sizeof(info))); ASSERT_TRUE(LoggingWriteFile(WritePipeHandle(), &info, sizeof(info)));
// If the current ptrace_scope is restricted, the broker needs to be set // 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. // ptracer allows the broker to inherit this condition.
ScopedPrSetPtracer set_ptracer(getpid(), /* may_log= */ true); 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); ASSERT_EQ(client.RequestCrashDump(info), 0);
} }
@ -239,16 +274,18 @@ class ExceptionHandlerServerTest : public testing::Test {
void ExpectCrashDumpUsingStrategy(PtraceStrategyDecider::Strategy strategy, void ExpectCrashDumpUsingStrategy(PtraceStrategyDecider::Strategy strategy,
bool succeeds) { bool succeeds) {
ScopedStopServerAndJoinThread stop_server(Server(), ServerThread());
ServerThread()->Start();
Server()->SetPtraceStrategyDecider( Server()->SetPtraceStrategyDecider(
std::make_unique<MockPtraceStrategyDecider>(strategy)); std::make_unique<MockPtraceStrategyDecider>(strategy));
ScopedStopServerAndJoinThread stop_server(Server(), ServerThread());
ServerThread()->Start();
CrashDumpTest test(this, succeeds); CrashDumpTest test(this, succeeds);
test.Run(); test.Run();
} }
bool UsingMultiClientSocket() const { return use_multi_client_socket_; }
protected: protected:
void SetUp() override { void SetUp() override {
int socks[2]; int socks[2];
@ -256,7 +293,8 @@ class ExceptionHandlerServerTest : public testing::Test {
sock_to_handler_.reset(socks[0]); sock_to_handler_.reset(socks[0]);
sock_to_client_ = socks[1]; sock_to_client_ = socks[1];
ASSERT_TRUE(server_.InitializeWithClient(ScopedFileHandle(socks[1]))); ASSERT_TRUE(server_.InitializeWithClient(ScopedFileHandle(socks[1]),
use_multi_client_socket_));
} }
private: private:
@ -265,36 +303,37 @@ class ExceptionHandlerServerTest : public testing::Test {
RunServerThread server_thread_; RunServerThread server_thread_;
ScopedFileHandle sock_to_handler_; ScopedFileHandle sock_to_handler_;
int sock_to_client_; int sock_to_client_;
bool use_multi_client_socket_;
DISALLOW_COPY_AND_ASSIGN(ExceptionHandlerServerTest); DISALLOW_COPY_AND_ASSIGN(ExceptionHandlerServerTest);
}; };
TEST_F(ExceptionHandlerServerTest, ShutdownWithNoClients) { TEST_P(ExceptionHandlerServerTest, ShutdownWithNoClients) {
ServerThread()->Start(); ServerThread()->Start();
Hangup(); Hangup();
ASSERT_TRUE(ServerThread()->JoinWithTimeout(5.0)); ASSERT_TRUE(ServerThread()->JoinWithTimeout(5.0));
} }
TEST_F(ExceptionHandlerServerTest, StopWithClients) { TEST_P(ExceptionHandlerServerTest, StopWithClients) {
ServerThread()->Start(); ServerThread()->Start();
Server()->Stop(); Server()->Stop();
ASSERT_TRUE(ServerThread()->JoinWithTimeout(5.0)); ASSERT_TRUE(ServerThread()->JoinWithTimeout(5.0));
} }
TEST_F(ExceptionHandlerServerTest, StopBeforeRun) { TEST_P(ExceptionHandlerServerTest, StopBeforeRun) {
Server()->Stop(); Server()->Stop();
ServerThread()->Start(); ServerThread()->Start();
ASSERT_TRUE(ServerThread()->JoinWithTimeout(5.0)); ASSERT_TRUE(ServerThread()->JoinWithTimeout(5.0));
} }
TEST_F(ExceptionHandlerServerTest, MultipleStops) { TEST_P(ExceptionHandlerServerTest, MultipleStops) {
ServerThread()->Start(); ServerThread()->Start();
Server()->Stop(); Server()->Stop();
Server()->Stop(); Server()->Stop();
ASSERT_TRUE(ServerThread()->JoinWithTimeout(5.0)); ASSERT_TRUE(ServerThread()->JoinWithTimeout(5.0));
} }
TEST_F(ExceptionHandlerServerTest, RequestCrashDumpDefault) { TEST_P(ExceptionHandlerServerTest, RequestCrashDumpDefault) {
ScopedStopServerAndJoinThread stop_server(Server(), ServerThread()); ScopedStopServerAndJoinThread stop_server(Server(), ServerThread());
ServerThread()->Start(); ServerThread()->Start();
@ -302,25 +341,35 @@ TEST_F(ExceptionHandlerServerTest, RequestCrashDumpDefault) {
test.Run(); test.Run();
} }
TEST_F(ExceptionHandlerServerTest, RequestCrashDumpNoPtrace) { TEST_P(ExceptionHandlerServerTest, RequestCrashDumpNoPtrace) {
ExpectCrashDumpUsingStrategy(PtraceStrategyDecider::Strategy::kNoPtrace, ExpectCrashDumpUsingStrategy(PtraceStrategyDecider::Strategy::kNoPtrace,
false); 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, ExpectCrashDumpUsingStrategy(PtraceStrategyDecider::Strategy::kUseBroker,
true); true);
} }
TEST_F(ExceptionHandlerServerTest, RequestCrashDumpDirectPtrace) { TEST_P(ExceptionHandlerServerTest, RequestCrashDumpDirectPtrace) {
ExpectCrashDumpUsingStrategy(PtraceStrategyDecider::Strategy::kDirectPtrace, ExpectCrashDumpUsingStrategy(PtraceStrategyDecider::Strategy::kDirectPtrace,
true); true);
} }
TEST_F(ExceptionHandlerServerTest, RequestCrashDumpError) { TEST_P(ExceptionHandlerServerTest, RequestCrashDumpError) {
ExpectCrashDumpUsingStrategy(PtraceStrategyDecider::Strategy::kError, false); ExpectCrashDumpUsingStrategy(PtraceStrategyDecider::Strategy::kError, false);
} }
INSTANTIATE_TEST_SUITE_P(ExceptionHandlerServerTestSuite,
ExceptionHandlerServerTest,
testing::Bool()
);
} // namespace } // namespace
} // namespace test } // namespace test
} // namespace crashpad } // namespace crashpad

Some files were not shown because too many files have changed in this diff Show More