mirror of
https://github.com/chromium/crashpad.git
synced 2025-03-20 10:43:46 +00:00
Merge master e50ea6032180 into doc
This commit is contained in:
commit
c46a62337d
9
.gitignore
vendored
9
.gitignore
vendored
@ -10,9 +10,16 @@
|
||||
.gdbinit
|
||||
/Makefile
|
||||
/out
|
||||
/third_party/fuchsia/.cipd
|
||||
/third_party/fuchsia/clang
|
||||
/third_party/fuchsia/qemu
|
||||
/third_party/fuchsia/sdk
|
||||
/third_party/gtest/gtest
|
||||
/third_party/libfuzzer
|
||||
/third_party/linux/.cipd
|
||||
/third_party/linux/clang
|
||||
/third_party/linux/sysroot
|
||||
/third_party/gyp/gyp
|
||||
/third_party/llvm
|
||||
/third_party/mini_chromium/mini_chromium
|
||||
/third_party/zlib/zlib
|
||||
/xcodebuild
|
||||
|
15
.gn
Normal file
15
.gn
Normal file
@ -0,0 +1,15 @@
|
||||
# Copyright 2017 The Crashpad Authors. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
buildconfig = "//build/BUILDCONFIG.gn"
|
4
AUTHORS
4
AUTHORS
@ -7,4 +7,8 @@
|
||||
# The email address is not required for organizations.
|
||||
|
||||
Google Inc.
|
||||
Intel Corporation
|
||||
Opera Software ASA
|
||||
Vewd Software AS
|
||||
LG Electronics, Inc.
|
||||
MIPS Technologies, Inc.
|
||||
|
178
BUILD.gn
Normal file
178
BUILD.gn
Normal file
@ -0,0 +1,178 @@
|
||||
# Copyright 2017 The Crashpad Authors. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import("build/crashpad_buildconfig.gni")
|
||||
import("build/test.gni")
|
||||
|
||||
config("crashpad_config") {
|
||||
include_dirs = [ "." ]
|
||||
}
|
||||
|
||||
if (crashpad_is_in_chromium || crashpad_is_in_fuchsia) {
|
||||
test("crashpad_tests") {
|
||||
deps = [
|
||||
"client:client_test",
|
||||
"handler:handler_test",
|
||||
"minidump:minidump_test",
|
||||
"snapshot:snapshot_test",
|
||||
"test:gmock_main",
|
||||
"test:test_test",
|
||||
"util:util_test",
|
||||
]
|
||||
}
|
||||
|
||||
if (crashpad_is_in_fuchsia) {
|
||||
import("//build/package.gni")
|
||||
package("crashpad_test") {
|
||||
testonly = true
|
||||
deprecated_system_image = true
|
||||
|
||||
deps = [
|
||||
":crashpad_tests",
|
||||
"snapshot:crashpad_snapshot_test_both_dt_hash_styles",
|
||||
"snapshot:crashpad_snapshot_test_module",
|
||||
"snapshot:crashpad_snapshot_test_module_large",
|
||||
"snapshot:crashpad_snapshot_test_module_small",
|
||||
"test:crashpad_test_test_multiprocess_exec_test_child",
|
||||
"util:generate_test_server_key",
|
||||
"util:http_transport_test_server",
|
||||
]
|
||||
|
||||
tests = [
|
||||
{
|
||||
name = "crashpad_tests"
|
||||
},
|
||||
{
|
||||
name = "crashpad_test_test_multiprocess_exec_test_child"
|
||||
dest = "crashpad_test_data/crashpad_test_test_multiprocess_exec_test_child"
|
||||
},
|
||||
{
|
||||
name = "http_transport_test_server"
|
||||
dest = "crashpad_test_data/http_transport_test_server"
|
||||
},
|
||||
|
||||
# These aren't actually tests, but that seems to be the only way to
|
||||
# convince package() to get them from the output directory.
|
||||
{
|
||||
name = "crashpad_util_test_cert.pem"
|
||||
dest = "crashpad_test_data/crashpad_util_test_cert.pem"
|
||||
},
|
||||
{
|
||||
name = "crashpad_util_test_key.pem"
|
||||
dest = "crashpad_test_data/crashpad_util_test_key.pem"
|
||||
},
|
||||
]
|
||||
|
||||
loadable_modules = [
|
||||
{
|
||||
name = "crashpad_snapshot_test_both_dt_hash_styles.so"
|
||||
},
|
||||
{
|
||||
name = "crashpad_snapshot_test_module.so"
|
||||
},
|
||||
{
|
||||
name = "crashpad_snapshot_test_module_large.so"
|
||||
},
|
||||
{
|
||||
name = "crashpad_snapshot_test_module_small.so"
|
||||
},
|
||||
]
|
||||
|
||||
resources = [
|
||||
{
|
||||
path = "util/net/testdata/ascii_http_body.txt"
|
||||
dest = "crashpad_test_data/util/net/testdata/ascii_http_body.txt"
|
||||
},
|
||||
{
|
||||
path = "util/net/testdata/binary_http_body.dat"
|
||||
dest = "crashpad_test_data/util/net/testdata/binary_http_body.dat"
|
||||
},
|
||||
{
|
||||
path = "test/test_paths_test_data_root.txt"
|
||||
dest = "crashpad_test_data/test/test_paths_test_data_root.txt"
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
package("crashpad_handler") {
|
||||
deprecated_system_image = true
|
||||
|
||||
deps = [
|
||||
"handler:crashpad_handler",
|
||||
]
|
||||
|
||||
binaries = [
|
||||
{
|
||||
name = "crashpad_handler"
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
package("crashpad_database_util") {
|
||||
deprecated_system_image = true
|
||||
|
||||
deps = [
|
||||
"tools:crashpad_database_util",
|
||||
]
|
||||
|
||||
binaries = [
|
||||
{
|
||||
name = "crashpad_database_util"
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
} else if (crashpad_is_standalone) {
|
||||
test("crashpad_client_test") {
|
||||
deps = [
|
||||
"client:client_test",
|
||||
"test:gmock_main",
|
||||
]
|
||||
}
|
||||
|
||||
test("crashpad_handler_test") {
|
||||
deps = [
|
||||
"handler:handler_test",
|
||||
"test:gtest_main",
|
||||
]
|
||||
}
|
||||
|
||||
test("crashpad_minidump_test") {
|
||||
deps = [
|
||||
"minidump:minidump_test",
|
||||
"test:gtest_main",
|
||||
]
|
||||
}
|
||||
|
||||
test("crashpad_snapshot_test") {
|
||||
deps = [
|
||||
"snapshot:snapshot_test",
|
||||
"test:gtest_main",
|
||||
]
|
||||
}
|
||||
|
||||
test("crashpad_test_test") {
|
||||
deps = [
|
||||
"test:gmock_main",
|
||||
"test:test_test",
|
||||
]
|
||||
}
|
||||
|
||||
test("crashpad_util_test") {
|
||||
deps = [
|
||||
"test:gmock_main",
|
||||
"util:util_test",
|
||||
]
|
||||
}
|
||||
}
|
208
DEPS
208
DEPS
@ -14,31 +14,26 @@
|
||||
|
||||
vars = {
|
||||
'chromium_git': 'https://chromium.googlesource.com',
|
||||
'pull_linux_clang': False,
|
||||
'pull_win_toolchain': False
|
||||
}
|
||||
|
||||
deps = {
|
||||
'buildtools':
|
||||
Var('chromium_git') + '/chromium/buildtools.git@' +
|
||||
'f6d165d9d842ddd29056c127a5f3a3c5d8e0d2e3',
|
||||
'6fe4a3251488f7af86d64fc25cf442e817cf6133',
|
||||
'crashpad/third_party/gtest/gtest':
|
||||
Var('chromium_git') + '/external/github.com/google/googletest@' +
|
||||
'7b6561c56e353100aca8458d7bc49c4e0119bae8',
|
||||
'c091b0469ab4c04ee9411ef770f32360945f4c53',
|
||||
'crashpad/third_party/gyp/gyp':
|
||||
Var('chromium_git') + '/external/gyp@' +
|
||||
'f72586209ecbf70b71ce690f2182ebe51669cbb3',
|
||||
|
||||
# TODO(scottmg): Consider pinning these. For now, we don't have any particular
|
||||
# reason to do so.
|
||||
'crashpad/third_party/llvm':
|
||||
Var('chromium_git') + '/external/llvm.org/llvm.git@HEAD',
|
||||
'crashpad/third_party/llvm/tools/clang':
|
||||
Var('chromium_git') + '/external/llvm.org/clang.git@HEAD',
|
||||
'crashpad/third_party/llvm/tools/lldb':
|
||||
Var('chromium_git') + '/external/llvm.org/lldb.git@HEAD',
|
||||
|
||||
'5e2b3ddde7cda5eb6bc09a5546a76b00e49d888f',
|
||||
'crashpad/third_party/mini_chromium/mini_chromium':
|
||||
Var('chromium_git') + '/chromium/mini_chromium@' +
|
||||
'7d6697ceb5cb5ca02fde3813496f48b9b1d76d0c',
|
||||
'793e94e2c652831af2d25bb5288b04e59048c62d',
|
||||
'crashpad/third_party/libfuzzer/src':
|
||||
Var('chromium_git') + '/chromium/llvm-project/compiler-rt/lib/fuzzer.git@' +
|
||||
'fda403cf93ecb8792cb1d061564d89a6553ca020',
|
||||
'crashpad/third_party/zlib/zlib':
|
||||
Var('chromium_git') + '/chromium/src/third_party/zlib@' +
|
||||
'13dc246a58e4b72104d35f9b1809af95221ebda7',
|
||||
@ -48,9 +43,9 @@ hooks = [
|
||||
{
|
||||
'name': 'clang_format_mac',
|
||||
'pattern': '.',
|
||||
'condition': 'host_os == "mac"',
|
||||
'action': [
|
||||
'download_from_google_storage',
|
||||
'--platform=^darwin$',
|
||||
'--no_resume',
|
||||
'--no_auth',
|
||||
'--bucket=chromium-clang-format',
|
||||
@ -58,25 +53,12 @@ hooks = [
|
||||
'buildtools/mac/clang-format.sha1',
|
||||
],
|
||||
},
|
||||
{
|
||||
'name': 'clang_format_win',
|
||||
'pattern': '.',
|
||||
'action': [
|
||||
'download_from_google_storage',
|
||||
'--platform=^win32$',
|
||||
'--no_resume',
|
||||
'--no_auth',
|
||||
'--bucket=chromium-clang-format',
|
||||
'--sha1_file',
|
||||
'buildtools/win/clang-format.exe.sha1',
|
||||
],
|
||||
},
|
||||
{
|
||||
'name': 'clang_format_linux',
|
||||
'pattern': '.',
|
||||
'condition': 'host_os == "linux"',
|
||||
'action': [
|
||||
'download_from_google_storage',
|
||||
'--platform=^linux2?$',
|
||||
'--no_resume',
|
||||
'--no_auth',
|
||||
'--bucket=chromium-clang-format',
|
||||
@ -85,11 +67,24 @@ hooks = [
|
||||
],
|
||||
},
|
||||
{
|
||||
'name': 'gn_mac',
|
||||
'name': 'clang_format_win',
|
||||
'pattern': '.',
|
||||
'condition': 'host_os == "win"',
|
||||
'action': [
|
||||
'download_from_google_storage',
|
||||
'--no_resume',
|
||||
'--no_auth',
|
||||
'--bucket=chromium-clang-format',
|
||||
'--sha1_file',
|
||||
'buildtools/win/clang-format.exe.sha1',
|
||||
],
|
||||
},
|
||||
{
|
||||
'name': 'gn_mac',
|
||||
'pattern': '.',
|
||||
'condition': 'host_os == "mac"',
|
||||
'action': [
|
||||
'download_from_google_storage',
|
||||
'--platform=^darwin$',
|
||||
'--no_resume',
|
||||
'--no_auth',
|
||||
'--bucket=chromium-gn',
|
||||
@ -98,11 +93,24 @@ hooks = [
|
||||
],
|
||||
},
|
||||
{
|
||||
'name': 'gn_win',
|
||||
'name': 'gn_linux',
|
||||
'pattern': '.',
|
||||
'condition': 'host_os == "linux"',
|
||||
'action': [
|
||||
'download_from_google_storage',
|
||||
'--no_resume',
|
||||
'--no_auth',
|
||||
'--bucket=chromium-gn',
|
||||
'--sha1_file',
|
||||
'buildtools/linux64/gn.sha1',
|
||||
],
|
||||
},
|
||||
{
|
||||
'name': 'gn_win',
|
||||
'pattern': '.',
|
||||
'condition': 'host_os == "win"',
|
||||
'action': [
|
||||
'download_from_google_storage',
|
||||
'--platform=^win32$',
|
||||
'--no_resume',
|
||||
'--no_auth',
|
||||
'--bucket=chromium-gn',
|
||||
@ -111,16 +119,134 @@ hooks = [
|
||||
],
|
||||
},
|
||||
{
|
||||
'name': 'gn_linux',
|
||||
# This uses “cipd install” so that mac-amd64 and linux-amd64 can coexist
|
||||
# peacefully. “cipd ensure” would remove the macOS package when running on a
|
||||
# Linux build host and vice-versa. https://crbug.com/789364. This package is
|
||||
# only updated when the solution in .gclient includes an entry like:
|
||||
# "custom_vars": { "pull_linux_clang": True }
|
||||
# The ref used is "goma". This is like "latest", but is considered a more
|
||||
# stable latest by the Fuchsia toolchain team.
|
||||
'name': 'clang_linux',
|
||||
'pattern': '.',
|
||||
'condition': 'checkout_linux and pull_linux_clang',
|
||||
'action': [
|
||||
'download_from_google_storage',
|
||||
'--platform=^linux2?$',
|
||||
'--no_resume',
|
||||
'--no_auth',
|
||||
'--bucket=chromium-gn',
|
||||
'--sha1_file',
|
||||
'buildtools/linux64/gn.sha1',
|
||||
'cipd',
|
||||
'install',
|
||||
# sic, using Fuchsia team's generic build of clang for linux-amd64 to
|
||||
# build for linux-amd64 target too.
|
||||
'fuchsia/clang/linux-amd64',
|
||||
'goma',
|
||||
'-root', 'crashpad/third_party/linux/clang/linux-amd64',
|
||||
'-log-level', 'info',
|
||||
],
|
||||
},
|
||||
{
|
||||
# If using a local clang ("pull_linux_clang" above), also pull down a
|
||||
# sysroot.
|
||||
'name': 'sysroot_linux',
|
||||
'pattern': '.',
|
||||
'condition': 'checkout_linux and pull_linux_clang',
|
||||
'action': [
|
||||
'crashpad/build/install_linux_sysroot.py',
|
||||
],
|
||||
},
|
||||
{
|
||||
# Same rationale for using "install" rather than "ensure" as for first clang
|
||||
# package. https://crbug.com/789364.
|
||||
# Same rationale for using "goma" instead of "latest" as clang_linux above.
|
||||
'name': 'fuchsia_clang_mac',
|
||||
'pattern': '.',
|
||||
'condition': 'checkout_fuchsia and host_os == "mac"',
|
||||
'action': [
|
||||
'cipd',
|
||||
'install',
|
||||
'fuchsia/clang/mac-amd64',
|
||||
'goma',
|
||||
'-root', 'crashpad/third_party/fuchsia/clang/mac-amd64',
|
||||
'-log-level', 'info',
|
||||
],
|
||||
},
|
||||
{
|
||||
# Same rationale for using "install" rather than "ensure" as for first clang
|
||||
# package. https://crbug.com/789364.
|
||||
# Same rationale for using "goma" instead of "latest" as clang_linux above.
|
||||
'name': 'fuchsia_clang_linux',
|
||||
'pattern': '.',
|
||||
'condition': 'checkout_fuchsia and host_os == "linux"',
|
||||
'action': [
|
||||
'cipd',
|
||||
'install',
|
||||
'fuchsia/clang/linux-amd64',
|
||||
'goma',
|
||||
'-root', 'crashpad/third_party/fuchsia/clang/linux-amd64',
|
||||
'-log-level', 'info',
|
||||
],
|
||||
},
|
||||
{
|
||||
# Same rationale for using "install" rather than "ensure" as for clang
|
||||
# packages. https://crbug.com/789364.
|
||||
'name': 'fuchsia_qemu_mac',
|
||||
'pattern': '.',
|
||||
'condition': 'checkout_fuchsia and host_os == "mac"',
|
||||
'action': [
|
||||
'cipd',
|
||||
'install',
|
||||
'fuchsia/qemu/mac-amd64',
|
||||
'latest',
|
||||
'-root', 'crashpad/third_party/fuchsia/qemu/mac-amd64',
|
||||
'-log-level', 'info',
|
||||
],
|
||||
},
|
||||
{
|
||||
# Same rationale for using "install" rather than "ensure" as for clang
|
||||
# packages. https://crbug.com/789364.
|
||||
'name': 'fuchsia_qemu_linux',
|
||||
'pattern': '.',
|
||||
'condition': 'checkout_fuchsia and host_os == "linux"',
|
||||
'action': [
|
||||
'cipd',
|
||||
'install',
|
||||
'fuchsia/qemu/linux-amd64',
|
||||
'latest',
|
||||
'-root', 'crashpad/third_party/fuchsia/qemu/linux-amd64',
|
||||
'-log-level', 'info',
|
||||
],
|
||||
},
|
||||
{
|
||||
# The SDK is keyed to the host system because it contains build tools.
|
||||
# Currently, linux-amd64 is the only SDK published (see
|
||||
# https://chrome-infra-packages.appspot.com/#/?path=fuchsia/sdk). As long as
|
||||
# this is the case, use that SDK package even on other build hosts. The
|
||||
# sysroot (containing headers and libraries) and other components are
|
||||
# related to the target and should be functional with an appropriate
|
||||
# toolchain that runs on the build host (fuchsia_clang, above).
|
||||
'name': 'fuchsia_sdk',
|
||||
'pattern': '.',
|
||||
'condition': 'checkout_fuchsia',
|
||||
'action': [
|
||||
'cipd',
|
||||
'install',
|
||||
'fuchsia/sdk/linux-amd64',
|
||||
'latest',
|
||||
'-root', 'crashpad/third_party/fuchsia/sdk/linux-amd64',
|
||||
'-log-level', 'info',
|
||||
],
|
||||
},
|
||||
{
|
||||
'name': 'toolchain_win',
|
||||
'pattern': '.',
|
||||
# This package is only updated when the solution in .gclient includes an
|
||||
# entry like:
|
||||
# "custom_vars": { "pull_win_toolchain": True }
|
||||
# This is because the contained bits are not redistributable.
|
||||
'condition': 'checkout_win and pull_win_toolchain',
|
||||
'action': [
|
||||
'cipd',
|
||||
'install',
|
||||
'chrome_internal/third_party/sdk/windows',
|
||||
'uploaded:2018-06-13',
|
||||
'-root', 'crashpad/third_party/win/toolchain',
|
||||
'-log-level', 'info',
|
||||
],
|
||||
},
|
||||
{
|
||||
|
51
build/BUILD.gn
Normal file
51
build/BUILD.gn
Normal file
@ -0,0 +1,51 @@
|
||||
# Copyright 2017 The Crashpad Authors. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# When building in Chromium, these configs is used to set #defines that indicate
|
||||
# whether code is being built standalone, or in Chromium, or potentially in some
|
||||
# other configutation.
|
||||
|
||||
import("crashpad_buildconfig.gni")
|
||||
|
||||
config("crashpad_is_in_chromium") {
|
||||
if (crashpad_is_in_chromium) {
|
||||
defines = [ "CRASHPAD_IS_IN_CHROMIUM" ]
|
||||
}
|
||||
}
|
||||
|
||||
config("crashpad_is_in_fuchsia") {
|
||||
if (crashpad_is_in_fuchsia) {
|
||||
defines = [ "CRASHPAD_IS_IN_FUCHSIA" ]
|
||||
}
|
||||
}
|
||||
|
||||
group("default_exe_manifest_win") {
|
||||
if (crashpad_is_in_chromium) {
|
||||
deps = [
|
||||
"//build/win:default_exe_manifest",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
config("crashpad_fuzzer_flags") {
|
||||
cflags = [
|
||||
"-fsanitize=address",
|
||||
"-fsanitize-address-use-after-scope",
|
||||
"-fsanitize=fuzzer",
|
||||
]
|
||||
|
||||
ldflags = [
|
||||
"-fsanitize=address",
|
||||
]
|
||||
}
|
92
build/BUILDCONFIG.gn
Normal file
92
build/BUILDCONFIG.gn
Normal file
@ -0,0 +1,92 @@
|
||||
# Copyright 2017 The Crashpad Authors. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# Intentionally very minimal, so that Crashpad can build in-tree in a variety of
|
||||
# other projects, unrelated to the variables that are set in those projects'
|
||||
# BUILDCONFIG.gn. Do not add more variables here. Instead, make them available
|
||||
# in build/crashpad_buildconfig.gni if they must be globally available.
|
||||
|
||||
if (target_os == "") {
|
||||
target_os = host_os
|
||||
}
|
||||
|
||||
if (current_os == "") {
|
||||
current_os = target_os
|
||||
}
|
||||
|
||||
if (target_cpu == "") {
|
||||
target_cpu = host_cpu
|
||||
}
|
||||
|
||||
if (current_cpu == "") {
|
||||
current_cpu = target_cpu
|
||||
}
|
||||
|
||||
if (current_os == "win") {
|
||||
set_default_toolchain(
|
||||
"//third_party/mini_chromium/mini_chromium/build:msvc_toolchain_$current_cpu")
|
||||
} else {
|
||||
set_default_toolchain(
|
||||
"//third_party/mini_chromium/mini_chromium/build:gcc_like_toolchain")
|
||||
}
|
||||
|
||||
declare_args() {
|
||||
# When true, enables the debug configuration, with additional run-time checks
|
||||
# and logging. When false, enables the release configuration, with additional
|
||||
# optimizations.
|
||||
is_debug = false
|
||||
|
||||
# When true, build all code with -fsanitize=fuzzer, and enable various
|
||||
# *_fuzzer targets.
|
||||
crashpad_use_libfuzzer = false
|
||||
}
|
||||
|
||||
_default_configs = [
|
||||
"//third_party/mini_chromium/mini_chromium/build:default",
|
||||
"//third_party/mini_chromium/mini_chromium/build:Wexit_time_destructors",
|
||||
]
|
||||
|
||||
if (crashpad_use_libfuzzer) {
|
||||
_default_configs += [ "//build:crashpad_fuzzer_flags" ]
|
||||
}
|
||||
|
||||
_default_executable_configs =
|
||||
_default_configs + [
|
||||
"//third_party/mini_chromium/mini_chromium/build:executable",
|
||||
"//third_party/mini_chromium/mini_chromium/build:win_console",
|
||||
]
|
||||
|
||||
set_defaults("source_set") {
|
||||
configs = _default_configs
|
||||
}
|
||||
|
||||
set_defaults("static_library") {
|
||||
configs = _default_configs
|
||||
}
|
||||
|
||||
set_defaults("executable") {
|
||||
configs = _default_executable_configs
|
||||
}
|
||||
|
||||
set_defaults("loadable_module") {
|
||||
configs = _default_configs
|
||||
}
|
||||
|
||||
set_defaults("shared_library") {
|
||||
configs = _default_configs
|
||||
}
|
||||
|
||||
set_defaults("test") {
|
||||
configs = _default_executable_configs
|
||||
}
|
108
build/crashpad_buildconfig.gni
Normal file
108
build/crashpad_buildconfig.gni
Normal file
@ -0,0 +1,108 @@
|
||||
# Copyright 2017 The Crashpad Authors. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
declare_args() {
|
||||
# Determines various flavors of build configuration, and which concrete
|
||||
# targets to use for dependencies. Valid values are "standalone", "chromium",
|
||||
# and "fuchsia".
|
||||
crashpad_dependencies = "standalone"
|
||||
|
||||
if (defined(is_fuchsia_tree) && is_fuchsia_tree) {
|
||||
# Determines various flavors of build configuration, and which concrete
|
||||
# targets to use for dependencies. Valid values are "standalone",
|
||||
# "chromium", and "fuchsia". Defaulted to "fuchsia" because
|
||||
# "is_fuchsia_tree" is set.
|
||||
crashpad_dependencies = "fuchsia"
|
||||
}
|
||||
}
|
||||
|
||||
assert(
|
||||
crashpad_dependencies == "chromium" || crashpad_dependencies == "fuchsia" ||
|
||||
crashpad_dependencies == "standalone")
|
||||
|
||||
crashpad_is_in_chromium = crashpad_dependencies == "chromium"
|
||||
crashpad_is_in_fuchsia = crashpad_dependencies == "fuchsia"
|
||||
crashpad_is_standalone = crashpad_dependencies == "standalone"
|
||||
|
||||
if (crashpad_is_in_chromium) {
|
||||
crashpad_is_mac = is_mac
|
||||
crashpad_is_win = is_win
|
||||
crashpad_is_linux = is_linux
|
||||
crashpad_is_android = is_android
|
||||
crashpad_is_fuchsia = is_fuchsia
|
||||
|
||||
crashpad_is_posix = is_posix
|
||||
|
||||
crashpad_is_clang = is_clang
|
||||
} else {
|
||||
# Both standalone and in Fuchsia tree use mini_chromium, and it's mapped into
|
||||
# the same location in both cases.
|
||||
import("../third_party/mini_chromium/mini_chromium/build/compiler.gni")
|
||||
import("../third_party/mini_chromium/mini_chromium/build/platform.gni")
|
||||
crashpad_is_mac = mini_chromium_is_mac
|
||||
crashpad_is_win = mini_chromium_is_win
|
||||
crashpad_is_linux = mini_chromium_is_linux
|
||||
crashpad_is_android = mini_chromium_is_android
|
||||
crashpad_is_fuchsia = mini_chromium_is_fuchsia
|
||||
|
||||
crashpad_is_posix = mini_chromium_is_posix
|
||||
|
||||
crashpad_is_clang = mini_chromium_is_clang
|
||||
}
|
||||
|
||||
template("crashpad_executable") {
|
||||
executable(target_name) {
|
||||
forward_variables_from(invoker, "*", [ "configs", "remove_configs" ])
|
||||
if (defined(invoker.remove_configs)) {
|
||||
configs -= invoker.remove_configs
|
||||
}
|
||||
|
||||
if (defined(invoker.configs)) {
|
||||
configs += invoker.configs
|
||||
}
|
||||
|
||||
if (!defined(deps)) {
|
||||
deps = []
|
||||
}
|
||||
|
||||
if (crashpad_is_in_chromium) {
|
||||
deps += [ "//build/config:exe_and_shlib_deps" ]
|
||||
} else if (crashpad_is_in_fuchsia) {
|
||||
configs += [ "//build/config/fuchsia:fdio_config" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template("crashpad_loadable_module") {
|
||||
loadable_module(target_name) {
|
||||
forward_variables_from(invoker, "*", [ "configs", "remove_configs" ])
|
||||
if (defined(invoker.remove_configs)) {
|
||||
configs -= invoker.remove_configs
|
||||
}
|
||||
|
||||
if (defined(invoker.configs)) {
|
||||
configs += invoker.configs
|
||||
}
|
||||
|
||||
if (!defined(deps)) {
|
||||
deps = []
|
||||
}
|
||||
|
||||
if (crashpad_is_in_chromium) {
|
||||
deps += [ "//build/config:exe_and_shlib_deps" ]
|
||||
} else if (crashpad_is_in_fuchsia) {
|
||||
configs += [ "//build/config/fuchsia:fdio_config" ]
|
||||
}
|
||||
}
|
||||
}
|
53
build/crashpad_dependencies.gni
Normal file
53
build/crashpad_dependencies.gni
Normal file
@ -0,0 +1,53 @@
|
||||
# Copyright 2017 The Crashpad Authors. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
declare_args() {
|
||||
# Determines various flavors of build configuration, and which concrete
|
||||
# targets to use for dependencies. Valid values are "standalone", "chromium",
|
||||
# and "fuchsia".
|
||||
crashpad_dependencies = "standalone"
|
||||
}
|
||||
|
||||
assert(
|
||||
crashpad_dependencies == "chromium" || crashpad_dependencies == "fuchsia" ||
|
||||
crashpad_dependencies == "standalone")
|
||||
|
||||
crashpad_is_in_chromium = crashpad_dependencies == "chromium"
|
||||
crashpad_is_in_fuchsia = crashpad_dependencies == "fuchsia"
|
||||
crashpad_is_standalone = crashpad_dependencies == "standalone"
|
||||
|
||||
if (crashpad_is_in_chromium) {
|
||||
crashpad_is_posix = is_posix
|
||||
} else if (crashpad_is_in_fuchsia) {
|
||||
import("//third_party/mini_chromium/build/is_posix.gni")
|
||||
crashpad_is_posix = mini_chromium_is_posix
|
||||
} else if (crashpad_is_standalone) {
|
||||
import("../third_party/mini_chromium/mini_chromium/build/is_posix.gni")
|
||||
crashpad_is_posix = mini_chromium_is_posix
|
||||
}
|
||||
|
||||
if (crashpad_is_in_chromium) {
|
||||
import("//testing/test.gni")
|
||||
} else {
|
||||
template("test") {
|
||||
executable(target_name) {
|
||||
testonly = true
|
||||
forward_variables_from(invoker, "*")
|
||||
}
|
||||
}
|
||||
|
||||
set_defaults("test") {
|
||||
configs = default_executable_configs
|
||||
}
|
||||
}
|
46
build/crashpad_fuzzer_test.gni
Normal file
46
build/crashpad_fuzzer_test.gni
Normal file
@ -0,0 +1,46 @@
|
||||
# Copyright 2018 The Crashpad Authors. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import("crashpad_buildconfig.gni")
|
||||
import("test.gni")
|
||||
|
||||
template("fuzzer_test") {
|
||||
if (crashpad_is_standalone && crashpad_use_libfuzzer) {
|
||||
test(target_name) {
|
||||
forward_variables_from(invoker,
|
||||
[
|
||||
"cflags",
|
||||
"cflags_cc",
|
||||
"check_includes",
|
||||
"defines",
|
||||
"include_dirs",
|
||||
"sources",
|
||||
])
|
||||
configs += [ "..:crashpad_config" ]
|
||||
if (defined(invoker.deps)) {
|
||||
deps = invoker.deps
|
||||
}
|
||||
deps += [ "../third_party/libfuzzer" ]
|
||||
|
||||
if (!defined(invoker.cflags)) {
|
||||
cflags = []
|
||||
}
|
||||
cflags += [ "-fsanitize=fuzzer" ]
|
||||
}
|
||||
} else {
|
||||
not_needed(invoker, "*")
|
||||
group(target_name) {
|
||||
}
|
||||
}
|
||||
}
|
@ -78,7 +78,7 @@ def main(args):
|
||||
# Check to make sure that no target_arch was specified. target_arch may be
|
||||
# set during a cross build, such as a cross build for Android.
|
||||
has_target_arch = False
|
||||
for arg_index in xrange(0, len(args)):
|
||||
for arg_index in range(0, len(args)):
|
||||
arg = args[arg_index]
|
||||
if (arg.startswith('-Dtarget_arch=') or
|
||||
(arg == '-D' and arg_index + 1 < len(args) and
|
||||
|
@ -69,26 +69,32 @@ def main(args):
|
||||
os.environ['CXX_target'] = os.path.join(ndk_bin_dir,
|
||||
'%s-g++' % arch_triplet)
|
||||
|
||||
# Unlike the Clang build, when using GCC with “unified headers,”
|
||||
# __ANDROID_API__ isn’t set automatically and must be pushed in to the
|
||||
# build. Fish the correct value out of the Clang wrapper script. If unified
|
||||
# headers are not being used, the Clang wrapper won’t mention
|
||||
# __ANDROID_API__, but the standalone toolchain’s <android/api-level.h> will
|
||||
# #define it for both Clang and GCC.
|
||||
#
|
||||
# Unified headers are the way of the future, according to
|
||||
# https://android.googlesource.com/platform/ndk/+/ndk-r14/CHANGELOG.md and
|
||||
# https://android.googlesource.com/platform/ndk/+/master/docs/UnifiedHeaders.md.
|
||||
with open(clang_path, 'r') as file:
|
||||
clang_script_contents = file.read()
|
||||
matches = re.finditer(r'\s-D__ANDROID_API__=([\d]+)\s',
|
||||
clang_script_contents)
|
||||
match = next(matches, None)
|
||||
if match:
|
||||
android_api = int(match.group(1))
|
||||
extra_args.extend(['-D', 'android_api_level=%d' % android_api])
|
||||
if next(matches, None):
|
||||
raise AssertionError('__ANDROID_API__ defined too many times')
|
||||
# Unlike the Clang build, when using GCC with unified headers, __ANDROID_API__
|
||||
# isn’t set automatically and must be pushed in to the build. Fish the correct
|
||||
# value out of the Clang wrapper script. If deprecated headers are in use, the
|
||||
# Clang wrapper won’t mention __ANDROID_API__, but the standalone toolchain’s
|
||||
# <android/api-level.h> will #define it for both Clang and GCC.
|
||||
#
|
||||
# android_api_level is extracted in this manner even when compiling with Clang
|
||||
# so that it’s available for use in GYP conditions that need to test the API
|
||||
# level, but beware that it’ll only be available when unified headers are in
|
||||
# use.
|
||||
#
|
||||
# Unified headers are the way of the future, according to
|
||||
# https://android.googlesource.com/platform/ndk/+/ndk-r14/CHANGELOG.md and
|
||||
# https://android.googlesource.com/platform/ndk/+/master/docs/UnifiedHeaders.md.
|
||||
# Traditional (deprecated) headers have been removed entirely as of NDK r16.
|
||||
# https://android.googlesource.com/platform/ndk/+/ndk-release-r16/CHANGELOG.md.
|
||||
with open(clang_path, 'r') as file:
|
||||
clang_script_contents = file.read()
|
||||
matches = re.finditer(r'\s-D__ANDROID_API__=([\d]+)\s',
|
||||
clang_script_contents)
|
||||
match = next(matches, None)
|
||||
if match:
|
||||
android_api = int(match.group(1))
|
||||
extra_args.extend(['-D', 'android_api_level=%d' % android_api])
|
||||
if next(matches, None):
|
||||
raise AssertionError('__ANDROID_API__ defined too many times')
|
||||
|
||||
for tool in ('ar', 'nm', 'readelf'):
|
||||
os.environ['%s_target' % tool.upper()] = (
|
||||
|
74
build/install_linux_sysroot.py
Executable file
74
build/install_linux_sysroot.py
Executable file
@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright 2018 The Crashpad Authors. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# Various code adapted from:
|
||||
# https://cs.chromium.org/chromium/src/build/linux/sysroot_scripts/install-sysroot.py
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import urllib2
|
||||
|
||||
|
||||
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
# Sysroot revision from:
|
||||
# https://cs.chromium.org/chromium/src/build/linux/sysroot_scripts/sysroots.json
|
||||
SERVER = 'https://commondatastorage.googleapis.com'
|
||||
PATH = 'chrome-linux-sysroot/toolchain'
|
||||
REVISION = '3c248ba4290a5ad07085b7af07e6785bf1ae5b66'
|
||||
FILENAME = 'debian_stretch_amd64_sysroot.tar.xz'
|
||||
|
||||
def main():
|
||||
url = '%s/%s/%s/%s' % (SERVER, PATH, REVISION, FILENAME)
|
||||
|
||||
sysroot = os.path.join(SCRIPT_DIR, os.pardir,
|
||||
'third_party', 'linux', 'sysroot')
|
||||
|
||||
stamp = os.path.join(sysroot, '.stamp')
|
||||
if os.path.exists(stamp):
|
||||
with open(stamp) as s:
|
||||
if s.read() == url:
|
||||
return
|
||||
|
||||
print 'Installing Debian root image from %s' % url
|
||||
|
||||
if os.path.isdir(sysroot):
|
||||
shutil.rmtree(sysroot)
|
||||
os.mkdir(sysroot)
|
||||
tarball = os.path.join(sysroot, FILENAME)
|
||||
print 'Downloading %s' % url
|
||||
|
||||
for _ in range(3):
|
||||
response = urllib2.urlopen(url)
|
||||
with open(tarball, 'wb') as f:
|
||||
f.write(response.read())
|
||||
break
|
||||
else:
|
||||
raise Exception('Failed to download %s' % url)
|
||||
|
||||
subprocess.check_call(['tar', 'xf', tarball, '-C', sysroot])
|
||||
|
||||
os.remove(tarball)
|
||||
|
||||
with open(stamp, 'w') as s:
|
||||
s.write(url)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
sys.exit(0)
|
134
build/run_fuchsia_qemu.py
Executable file
134
build/run_fuchsia_qemu.py
Executable file
@ -0,0 +1,134 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright 2017 The Crashpad Authors. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Helper script to [re]start or stop a helper Fuchsia QEMU instance to be used
|
||||
for running tests without a device.
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import getpass
|
||||
import os
|
||||
import random
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
try:
|
||||
from subprocess import DEVNULL
|
||||
except ImportError:
|
||||
DEVNULL = open(os.devnull, 'r+b')
|
||||
|
||||
CRASHPAD_ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
||||
os.pardir)
|
||||
|
||||
|
||||
def _Stop(pid_file):
|
||||
if os.path.isfile(pid_file):
|
||||
with open(pid_file, 'rb') as f:
|
||||
pid = int(f.read().strip())
|
||||
try:
|
||||
os.kill(pid, signal.SIGTERM)
|
||||
except:
|
||||
print('Unable to kill pid %d, continuing' % pid, file=sys.stderr)
|
||||
os.unlink(pid_file)
|
||||
|
||||
|
||||
def _CheckForTun():
|
||||
"""Check for networking. TODO(scottmg): Currently, this is Linux-specific.
|
||||
"""
|
||||
returncode = subprocess.call(
|
||||
['tunctl', '-b', '-u', getpass.getuser(), '-t', 'qemu'],
|
||||
stdout=DEVNULL, stderr=DEVNULL)
|
||||
if returncode != 0:
|
||||
print('To use QEMU with networking on Linux, configure TUN/TAP. See:',
|
||||
file=sys.stderr)
|
||||
print(' https://fuchsia.googlesource.com/zircon/+/HEAD/docs/qemu.md#enabling-networking-under-qemu-x86_64-only',
|
||||
file=sys.stderr)
|
||||
return 2
|
||||
return 0
|
||||
|
||||
|
||||
def _Start(pid_file):
|
||||
tun_result = _CheckForTun()
|
||||
if tun_result != 0:
|
||||
return tun_result
|
||||
|
||||
arch = 'mac-amd64' if sys.platform == 'darwin' else 'linux-amd64'
|
||||
fuchsia_dir = os.path.join(CRASHPAD_ROOT, 'third_party', 'fuchsia')
|
||||
qemu_path = os.path.join(fuchsia_dir, 'qemu', arch, 'bin',
|
||||
'qemu-system-x86_64')
|
||||
kernel_data_dir = os.path.join(fuchsia_dir, 'sdk', arch, 'target', 'x86_64')
|
||||
kernel_path = os.path.join(kernel_data_dir, 'zircon.bin')
|
||||
initrd_path = os.path.join(kernel_data_dir, 'bootdata.bin')
|
||||
|
||||
mac_tail = ':'.join('%02x' % random.randint(0, 255) for x in range(3))
|
||||
instance_name = 'crashpad_qemu_' + \
|
||||
''.join(chr(random.randint(ord('A'), ord('Z'))) for x in range(8))
|
||||
|
||||
# These arguments are from the Fuchsia repo in zircon/scripts/run-zircon.
|
||||
popen = subprocess.Popen([
|
||||
qemu_path,
|
||||
'-m', '2048',
|
||||
'-nographic',
|
||||
'-kernel', kernel_path,
|
||||
'-initrd', initrd_path,
|
||||
'-smp', '4',
|
||||
'-serial', 'stdio',
|
||||
'-monitor', 'none',
|
||||
'-machine', 'q35',
|
||||
'-cpu', 'host,migratable=no',
|
||||
'-enable-kvm',
|
||||
'-netdev', 'type=tap,ifname=qemu,script=no,downscript=no,id=net0',
|
||||
'-device', 'e1000,netdev=net0,mac=52:54:00:' + mac_tail,
|
||||
'-append', 'TERM=dumb zircon.nodename=' + instance_name,
|
||||
], stdin=DEVNULL, stdout=DEVNULL, stderr=DEVNULL)
|
||||
|
||||
with open(pid_file, 'wb') as f:
|
||||
f.write('%d\n' % popen.pid)
|
||||
|
||||
for i in range(10):
|
||||
netaddr_path = os.path.join(fuchsia_dir, 'sdk', arch, 'tools', 'netaddr')
|
||||
if subprocess.call([netaddr_path, '--nowait', instance_name],
|
||||
stdout=open(os.devnull), stderr=open(os.devnull)) == 0:
|
||||
break
|
||||
time.sleep(.5)
|
||||
else:
|
||||
print('instance did not respond after start', file=sys.stderr)
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def main(args):
|
||||
if len(args) != 1 or args[0] not in ('start', 'stop'):
|
||||
print('usage: run_fuchsia_qemu.py start|stop', file=sys.stderr)
|
||||
return 1
|
||||
|
||||
command = args[0]
|
||||
|
||||
pid_file = os.path.join(tempfile.gettempdir(), 'crashpad_fuchsia_qemu_pid')
|
||||
_Stop(pid_file)
|
||||
if command == 'start':
|
||||
return _Start(pid_file)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main(sys.argv[1:]))
|
@ -15,22 +15,436 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import pipes
|
||||
import posixpath
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import uuid
|
||||
|
||||
CRASHPAD_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
||||
os.pardir)
|
||||
IS_WINDOWS_HOST = sys.platform.startswith('win')
|
||||
|
||||
|
||||
def _FindGNFromBinaryDir(binary_dir):
|
||||
"""Attempts to determine the path to a GN binary used to generate the build
|
||||
files in the given binary_dir. This is necessary because `gn` might not be in
|
||||
the path or might be in a non-standard location, particularly on build
|
||||
machines."""
|
||||
|
||||
build_ninja = os.path.join(binary_dir, 'build.ninja')
|
||||
if os.path.isfile(build_ninja):
|
||||
with open(build_ninja, 'rb') as f:
|
||||
# Look for the always-generated regeneration rule of the form:
|
||||
#
|
||||
# rule gn
|
||||
# command = <gn binary> ... arguments ...
|
||||
#
|
||||
# to extract the gn binary's full path.
|
||||
found_rule_gn = False
|
||||
for line in f:
|
||||
if line.strip() == 'rule gn':
|
||||
found_rule_gn = True
|
||||
continue
|
||||
if found_rule_gn:
|
||||
if len(line) == 0 or line[0] != ' ':
|
||||
return None
|
||||
if line.startswith(' command = '):
|
||||
gn_command_line_parts = line.strip().split(' ')
|
||||
if len(gn_command_line_parts) > 2:
|
||||
return os.path.join(binary_dir, gn_command_line_parts[2])
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _BinaryDirTargetOS(binary_dir):
|
||||
"""Returns the apparent target OS of binary_dir, or None if none appear to be
|
||||
explicitly specified."""
|
||||
|
||||
gn_path = _FindGNFromBinaryDir(binary_dir)
|
||||
|
||||
if gn_path:
|
||||
# Look for a GN “target_os”.
|
||||
popen = subprocess.Popen([gn_path, '--root=' + CRASHPAD_DIR,
|
||||
'args', binary_dir,
|
||||
'--list=target_os', '--short'],
|
||||
shell=IS_WINDOWS_HOST,
|
||||
stdout=subprocess.PIPE, stderr=open(os.devnull))
|
||||
value = popen.communicate()[0]
|
||||
if popen.returncode == 0:
|
||||
match = re.match('target_os = "(.*)"$', value.decode('utf-8'))
|
||||
if match:
|
||||
return match.group(1)
|
||||
|
||||
# For GYP with Ninja, look for the appearance of “linux-android” in the path
|
||||
# to ar. This path is configured by gyp_crashpad_android.py.
|
||||
build_ninja_path = os.path.join(binary_dir, 'build.ninja')
|
||||
if os.path.exists(build_ninja_path):
|
||||
with open(build_ninja_path) as build_ninja_file:
|
||||
build_ninja_content = build_ninja_file.read()
|
||||
match = re.search('^ar = .+-linux-android(eabi)?-ar$',
|
||||
build_ninja_content,
|
||||
re.MULTILINE)
|
||||
if match:
|
||||
return 'android'
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _EnableVTProcessingOnWindowsConsole():
|
||||
"""Enables virtual terminal processing for ANSI/VT100-style escape sequences
|
||||
on a Windows console attached to standard output. Returns True on success.
|
||||
Returns False if standard output is not a console or if virtual terminal
|
||||
processing is not supported. The feature was introduced in Windows 10.
|
||||
"""
|
||||
|
||||
import pywintypes
|
||||
import win32console
|
||||
import winerror
|
||||
|
||||
stdout_console = win32console.GetStdHandle(win32console.STD_OUTPUT_HANDLE)
|
||||
try:
|
||||
console_mode = stdout_console.GetConsoleMode()
|
||||
except pywintypes.error as e:
|
||||
if e.winerror == winerror.ERROR_INVALID_HANDLE:
|
||||
# Standard output is not a console.
|
||||
return False
|
||||
raise
|
||||
|
||||
try:
|
||||
# From <wincon.h>. This would be
|
||||
# win32console.ENABLE_VIRTUAL_TERMINAL_PROCESSING, but it’s too new to be
|
||||
# defined there.
|
||||
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
|
||||
|
||||
stdout_console.SetConsoleMode(console_mode |
|
||||
ENABLE_VIRTUAL_TERMINAL_PROCESSING)
|
||||
except pywintypes.error as e:
|
||||
if e.winerror == winerror.ERROR_INVALID_PARAMETER:
|
||||
# ANSI/VT100-style escape sequence processing isn’t supported before
|
||||
# Windows 10.
|
||||
return False
|
||||
raise
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def _RunOnAndroidTarget(binary_dir, test, android_device, extra_command_line):
|
||||
local_test_path = os.path.join(binary_dir, test)
|
||||
MAYBE_UNSUPPORTED_TESTS = (
|
||||
'crashpad_client_test',
|
||||
'crashpad_handler_test',
|
||||
'crashpad_minidump_test',
|
||||
'crashpad_snapshot_test',
|
||||
)
|
||||
if not os.path.exists(local_test_path) and test in MAYBE_UNSUPPORTED_TESTS:
|
||||
print('This test is not present and may not be supported, skipping')
|
||||
return
|
||||
|
||||
def _adb(*args):
|
||||
# Flush all of this script’s own buffered stdout output before running adb,
|
||||
# which will likely produce its own output on stdout.
|
||||
sys.stdout.flush()
|
||||
|
||||
adb_command = ['adb', '-s', android_device]
|
||||
adb_command.extend(args)
|
||||
subprocess.check_call(adb_command, shell=IS_WINDOWS_HOST)
|
||||
|
||||
def _adb_push(sources, destination):
|
||||
args = list(sources)
|
||||
args.append(destination)
|
||||
_adb('push', *args)
|
||||
|
||||
def _adb_shell(command_args, env={}):
|
||||
# Build a command to execute via “sh -c” instead of invoking it directly.
|
||||
# Here’s why:
|
||||
#
|
||||
# /system/bin/env isn’t normally present prior to Android 6.0 (M), where
|
||||
# toybox was introduced (Android platform/manifest 9a2c01e8450b). Instead,
|
||||
# set environment variables by using the shell’s internal “export” command.
|
||||
#
|
||||
# adbd prior to Android 7.0 (N), and the adb client prior to SDK
|
||||
# platform-tools version 24, don’t know how to communicate a shell command’s
|
||||
# exit status. This was added in Android platform/system/core 606835ae5c4b).
|
||||
# With older adb servers and clients, adb will “exit 0” indicating success
|
||||
# even if the command failed on the device. This makes
|
||||
# subprocess.check_call() semantics difficult to implement directly. As a
|
||||
# workaround, have the device send the command’s exit status over stdout and
|
||||
# pick it back up in this function.
|
||||
#
|
||||
# Both workarounds are implemented by giving the device a simple script,
|
||||
# which adbd will run as an “sh -c” argument.
|
||||
adb_command = ['adb', '-s', android_device, 'shell']
|
||||
script_commands = []
|
||||
for k, v in env.items():
|
||||
script_commands.append('export %s=%s' % (pipes.quote(k), pipes.quote(v)))
|
||||
script_commands.extend([
|
||||
' '.join(pipes.quote(x) for x in command_args),
|
||||
'status=${?}',
|
||||
'echo "status=${status}"',
|
||||
'exit ${status}'])
|
||||
adb_command.append('; '.join(script_commands))
|
||||
child = subprocess.Popen(adb_command,
|
||||
shell=IS_WINDOWS_HOST,
|
||||
stdin=open(os.devnull),
|
||||
stdout=subprocess.PIPE)
|
||||
|
||||
FINAL_LINE_RE = re.compile('status=(\d+)$')
|
||||
final_line = None
|
||||
while True:
|
||||
# Use readline so that the test output appears “live” when running.
|
||||
data = child.stdout.readline().decode('utf-8')
|
||||
if data == '':
|
||||
break
|
||||
if final_line is not None:
|
||||
# It wasn’t really the final line.
|
||||
print(final_line, end='')
|
||||
final_line = None
|
||||
if FINAL_LINE_RE.match(data.rstrip()):
|
||||
final_line = data
|
||||
else:
|
||||
print(data, end='')
|
||||
|
||||
if final_line is None:
|
||||
# Maybe there was some stderr output after the end of stdout. Old versions
|
||||
# of adb, prior to when the exit status could be communicated, smush the
|
||||
# two together.
|
||||
raise subprocess.CalledProcessError(-1, adb_command)
|
||||
status = int(FINAL_LINE_RE.match(final_line.rstrip()).group(1))
|
||||
if status != 0:
|
||||
raise subprocess.CalledProcessError(status, adb_command)
|
||||
|
||||
child.wait()
|
||||
if child.returncode != 0:
|
||||
raise subprocess.CalledProcessError(subprocess.returncode, adb_command)
|
||||
|
||||
# /system/bin/mktemp isn’t normally present prior to Android 6.0 (M), where
|
||||
# toybox was introduced (Android platform/manifest 9a2c01e8450b). Fake it with
|
||||
# a host-generated name. This won’t retry if the name is in use, but with 122
|
||||
# bits of randomness, it should be OK. This uses “mkdir” instead of “mkdir -p”
|
||||
# because the latter will not indicate failure if the directory already
|
||||
# exists.
|
||||
device_temp_dir = '/data/local/tmp/%s.%s' % (test, uuid.uuid4().hex)
|
||||
_adb_shell(['mkdir', device_temp_dir])
|
||||
|
||||
try:
|
||||
# Specify test dependencies that must be pushed to the device. This could be
|
||||
# determined automatically in a GN build, following the example used for
|
||||
# Fuchsia. Since nothing like that exists for GYP, hard-code it for
|
||||
# supported tests.
|
||||
test_build_artifacts = [test]
|
||||
test_data = ['test/test_paths_test_data_root.txt']
|
||||
|
||||
if test == 'crashpad_test_test':
|
||||
test_build_artifacts.append(
|
||||
'crashpad_test_test_multiprocess_exec_test_child')
|
||||
elif test == 'crashpad_util_test':
|
||||
test_data.append('util/net/testdata/')
|
||||
|
||||
# Establish the directory structure on the device.
|
||||
device_out_dir = posixpath.join(device_temp_dir, 'out')
|
||||
device_mkdirs = [device_out_dir]
|
||||
for source_path in test_data:
|
||||
# A trailing slash could reasonably mean to copy an entire directory, but
|
||||
# will interfere with what’s needed from the path split. All parent
|
||||
# directories of any source_path need to be be represented in
|
||||
# device_mkdirs, but it’s important that no source_path itself wind up in
|
||||
# device_mkdirs, even if source_path names a directory, because that would
|
||||
# cause the “adb push” of the directory below to behave incorrectly.
|
||||
if source_path.endswith(posixpath.sep):
|
||||
source_path = source_path[:-1]
|
||||
|
||||
device_source_path = posixpath.join(device_temp_dir, source_path)
|
||||
device_mkdir = posixpath.split(device_source_path)[0]
|
||||
if device_mkdir not in device_mkdirs:
|
||||
device_mkdirs.append(device_mkdir)
|
||||
adb_mkdir_command = ['mkdir', '-p']
|
||||
adb_mkdir_command.extend(device_mkdirs)
|
||||
_adb_shell(adb_mkdir_command)
|
||||
|
||||
# Push the test binary and any other build output to the device.
|
||||
local_test_build_artifacts = []
|
||||
for artifact in test_build_artifacts:
|
||||
local_test_build_artifacts.append(os.path.join(binary_dir, artifact))
|
||||
_adb_push(local_test_build_artifacts, device_out_dir)
|
||||
|
||||
# Push test data to the device.
|
||||
for source_path in test_data:
|
||||
_adb_push([os.path.join(CRASHPAD_DIR, source_path)],
|
||||
posixpath.join(device_temp_dir, source_path))
|
||||
|
||||
# Run the test on the device. Pass the test data root in the environment.
|
||||
#
|
||||
# Because the test will not run with its standard output attached to a
|
||||
# pseudo-terminal device, gtest will not normally enable colored output, so
|
||||
# mimic gtest’s own logic for deciding whether to enable color by checking
|
||||
# this script’s own standard output connection. The whitelist of TERM values
|
||||
# comes from gtest googletest/src/gtest.cc
|
||||
# testing::internal::ShouldUseColor().
|
||||
env = {'CRASHPAD_TEST_DATA_ROOT': device_temp_dir}
|
||||
gtest_color = os.environ.get('GTEST_COLOR')
|
||||
if gtest_color in ('auto', None):
|
||||
if (sys.stdout.isatty() and
|
||||
(os.environ.get('TERM') in
|
||||
('xterm', 'xterm-color', 'xterm-256color', 'screen',
|
||||
'screen-256color', 'tmux', 'tmux-256color', 'rxvt-unicode',
|
||||
'rxvt-unicode-256color', 'linux', 'cygwin') or
|
||||
(IS_WINDOWS_HOST and _EnableVTProcessingOnWindowsConsole()))):
|
||||
gtest_color = 'yes'
|
||||
else:
|
||||
gtest_color = 'no'
|
||||
env['GTEST_COLOR'] = gtest_color
|
||||
_adb_shell([posixpath.join(device_out_dir, test)] + extra_command_line, env)
|
||||
finally:
|
||||
_adb_shell(['rm', '-rf', device_temp_dir])
|
||||
|
||||
|
||||
def _GetFuchsiaSDKRoot():
|
||||
arch = 'mac-amd64' if sys.platform == 'darwin' else 'linux-amd64'
|
||||
return os.path.join(CRASHPAD_DIR, 'third_party', 'fuchsia', 'sdk', arch)
|
||||
|
||||
|
||||
def _GenerateFuchsiaRuntimeDepsFiles(binary_dir, tests):
|
||||
"""Ensures a <binary_dir>/<test>.runtime_deps file exists for each test."""
|
||||
targets_file = os.path.join(binary_dir, 'targets.txt')
|
||||
with open(targets_file, 'wb') as f:
|
||||
f.write('//:' + '\n//:'.join(tests) + '\n')
|
||||
gn_path = _FindGNFromBinaryDir(binary_dir)
|
||||
subprocess.check_call(
|
||||
[gn_path, '--root=' + CRASHPAD_DIR, 'gen', binary_dir,
|
||||
'--runtime-deps-list-file=' + targets_file])
|
||||
|
||||
# Run again so that --runtime-deps-list-file isn't in the regen rule. See
|
||||
# https://crbug.com/814816.
|
||||
subprocess.check_call(
|
||||
[gn_path, '--root=' + CRASHPAD_DIR, 'gen', binary_dir])
|
||||
|
||||
|
||||
def _HandleOutputFromFuchsiaLogListener(process, done_message):
|
||||
"""Pass through the output from |process| (which should be an instance of
|
||||
Fuchsia's loglistener) until a special termination |done_message| is
|
||||
encountered.
|
||||
|
||||
Also attempts to determine if any tests failed by inspecting the log output,
|
||||
and returns False if there were failures.
|
||||
"""
|
||||
success = True
|
||||
while True:
|
||||
line = process.stdout.readline().rstrip()
|
||||
if 'FAILED TEST' in line:
|
||||
success = False
|
||||
elif done_message in line and 'echo ' not in line:
|
||||
break
|
||||
print(line)
|
||||
return success
|
||||
|
||||
|
||||
def _RunOnFuchsiaTarget(binary_dir, test, device_name, extra_command_line):
|
||||
"""Runs the given Fuchsia |test| executable on the given |device_name|. The
|
||||
device must already be booted.
|
||||
|
||||
Copies the executable and its runtime dependencies as specified by GN to the
|
||||
target in /tmp using `netcp`, runs the binary on the target, and logs output
|
||||
back to stdout on this machine via `loglistener`.
|
||||
"""
|
||||
sdk_root = _GetFuchsiaSDKRoot()
|
||||
|
||||
# Run loglistener and filter the output to know when the test is done.
|
||||
loglistener_process = subprocess.Popen(
|
||||
[os.path.join(sdk_root, 'tools', 'loglistener'), device_name],
|
||||
stdout=subprocess.PIPE, stdin=open(os.devnull), stderr=open(os.devnull))
|
||||
|
||||
runtime_deps_file = os.path.join(binary_dir, test + '.runtime_deps')
|
||||
with open(runtime_deps_file, 'rb') as f:
|
||||
runtime_deps = f.read().splitlines()
|
||||
|
||||
def netruncmd(*args):
|
||||
"""Runs a list of commands on the target device. Each command is escaped
|
||||
by using pipes.quote(), and then each command is chained by shell ';'.
|
||||
"""
|
||||
netruncmd_path = os.path.join(sdk_root, 'tools', 'netruncmd')
|
||||
final_args = ' ; '.join(' '.join(pipes.quote(x) for x in command)
|
||||
for command in args)
|
||||
subprocess.check_call([netruncmd_path, device_name, final_args])
|
||||
|
||||
try:
|
||||
unique_id = uuid.uuid4().hex
|
||||
test_root = '/tmp/%s_%s' % (test, unique_id)
|
||||
tmp_root = test_root + '/tmp'
|
||||
staging_root = test_root + '/pkg'
|
||||
|
||||
# Make a staging directory tree on the target.
|
||||
directories_to_create = [tmp_root,
|
||||
'%s/bin' % staging_root,
|
||||
'%s/assets' % staging_root]
|
||||
netruncmd(['mkdir', '-p'] + directories_to_create)
|
||||
|
||||
def netcp(local_path):
|
||||
"""Uses `netcp` to copy a file or directory to the device. Files located
|
||||
inside the build dir are stored to /pkg/bin, otherwise to /pkg/assets.
|
||||
.so files are stored somewhere completely different, into /boot/lib (!).
|
||||
This is because the loader service does not yet correctly handle the
|
||||
namespace in which the caller is being run, and so can only load .so files
|
||||
from a couple hardcoded locations, the only writable one of which is
|
||||
/boot/lib, so we copy all .so files there. This bug is filed upstream as
|
||||
ZX-1619.
|
||||
"""
|
||||
in_binary_dir = local_path.startswith(binary_dir + '/')
|
||||
if in_binary_dir:
|
||||
if local_path.endswith('.so'):
|
||||
target_path = os.path.join(
|
||||
'/boot/lib', local_path[len(binary_dir)+1:])
|
||||
else:
|
||||
target_path = os.path.join(
|
||||
staging_root, 'bin', local_path[len(binary_dir)+1:])
|
||||
else:
|
||||
relative_path = os.path.relpath(local_path, CRASHPAD_DIR)
|
||||
target_path = os.path.join(staging_root, 'assets', relative_path)
|
||||
netcp_path = os.path.join(sdk_root, 'tools', 'netcp')
|
||||
subprocess.check_call([netcp_path, local_path,
|
||||
device_name + ':' + target_path],
|
||||
stderr=open(os.devnull))
|
||||
|
||||
# Copy runtime deps into the staging tree.
|
||||
for dep in runtime_deps:
|
||||
local_path = os.path.normpath(os.path.join(binary_dir, dep))
|
||||
if os.path.isdir(local_path):
|
||||
for root, dirs, files in os.walk(local_path):
|
||||
for f in files:
|
||||
netcp(os.path.join(root, f))
|
||||
else:
|
||||
netcp(local_path)
|
||||
|
||||
done_message = 'TERMINATED: ' + unique_id
|
||||
namespace_command = [
|
||||
'namespace', '/pkg=' + staging_root, '/tmp=' + tmp_root, '/svc=/svc',
|
||||
'--replace-child-argv0=/pkg/bin/' + test, '--',
|
||||
staging_root + '/bin/' + test] + extra_command_line
|
||||
netruncmd(namespace_command, ['echo', done_message])
|
||||
|
||||
success = _HandleOutputFromFuchsiaLogListener(
|
||||
loglistener_process, done_message)
|
||||
if not success:
|
||||
raise subprocess.CalledProcessError(1, test)
|
||||
finally:
|
||||
netruncmd(['rm', '-rf', test_root])
|
||||
|
||||
|
||||
# This script is primarily used from the waterfall so that the list of tests
|
||||
# that are run is maintained in-tree, rather than in a separate infrastructure
|
||||
# location in the recipe.
|
||||
def main(args):
|
||||
if len(args) != 1:
|
||||
print >> sys.stderr, 'usage: run_tests.py <binary_dir>'
|
||||
return 1
|
||||
|
||||
crashpad_dir = \
|
||||
os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir)
|
||||
binary_dir = args[0]
|
||||
parser = argparse.ArgumentParser(description='Run Crashpad unittests.')
|
||||
parser.add_argument('binary_dir', help='Root of build dir')
|
||||
parser.add_argument('test', nargs='*', help='Specific test(s) to run.')
|
||||
parser.add_argument('--gtest_filter',
|
||||
help='GTest filter applied to GTest binary runs.')
|
||||
args = parser.parse_args()
|
||||
|
||||
# Tell 64-bit Windows tests where to find 32-bit test executables, for
|
||||
# cross-bitted testing. This relies on the fact that the GYP build by default
|
||||
@ -38,37 +452,88 @@ def main(args):
|
||||
# 64-bit build. This is not a universally valid assumption, and if it’s not
|
||||
# met, 64-bit tests that require 32-bit build output will disable themselves
|
||||
# dynamically.
|
||||
if (sys.platform == 'win32' and binary_dir.endswith('_x64') and
|
||||
if (sys.platform == 'win32' and args.binary_dir.endswith('_x64') and
|
||||
'CRASHPAD_TEST_32_BIT_OUTPUT' not in os.environ):
|
||||
binary_dir_32 = binary_dir[:-4]
|
||||
binary_dir_32 = args.binary_dir[:-4]
|
||||
if os.path.isdir(binary_dir_32):
|
||||
os.environ['CRASHPAD_TEST_32_BIT_OUTPUT'] = binary_dir_32
|
||||
|
||||
target_os = _BinaryDirTargetOS(args.binary_dir)
|
||||
is_android = target_os == 'android'
|
||||
is_fuchsia = target_os == 'fuchsia'
|
||||
|
||||
tests = [
|
||||
'crashpad_client_test',
|
||||
'crashpad_handler_test',
|
||||
'crashpad_minidump_test',
|
||||
'crashpad_snapshot_test',
|
||||
'crashpad_test_test',
|
||||
'crashpad_util_test',
|
||||
]
|
||||
|
||||
if sys.platform == 'win32':
|
||||
tests.append('crashpad_handler_test')
|
||||
tests = sorted(tests)
|
||||
if is_android:
|
||||
android_device = os.environ.get('ANDROID_DEVICE')
|
||||
if not android_device:
|
||||
adb_devices = subprocess.check_output(['adb', 'devices'],
|
||||
shell=IS_WINDOWS_HOST)
|
||||
devices = []
|
||||
for line in adb_devices.splitlines():
|
||||
line = line.decode('utf-8')
|
||||
if (line == 'List of devices attached' or
|
||||
re.match('^\* daemon .+ \*$', line) or
|
||||
line == ''):
|
||||
continue
|
||||
(device, ignore) = line.split('\t')
|
||||
devices.append(device)
|
||||
if len(devices) != 1:
|
||||
print("Please set ANDROID_DEVICE to your device's id", file=sys.stderr)
|
||||
return 2
|
||||
android_device = devices[0]
|
||||
print('Using autodetected Android device:', android_device)
|
||||
elif is_fuchsia:
|
||||
zircon_nodename = os.environ.get('ZIRCON_NODENAME')
|
||||
if not zircon_nodename:
|
||||
netls = os.path.join(_GetFuchsiaSDKRoot(), 'tools', 'netls')
|
||||
popen = subprocess.Popen([netls, '--nowait'], stdout=subprocess.PIPE)
|
||||
devices = popen.communicate()[0].splitlines()
|
||||
if popen.returncode != 0 or len(devices) != 1:
|
||||
print("Please set ZIRCON_NODENAME to your device's hostname",
|
||||
file=sys.stderr)
|
||||
return 2
|
||||
zircon_nodename = devices[0].strip().split()[1]
|
||||
print('Using autodetected Fuchsia device:', zircon_nodename)
|
||||
_GenerateFuchsiaRuntimeDepsFiles(
|
||||
args.binary_dir, [t for t in tests if not t.endswith('.py')])
|
||||
elif IS_WINDOWS_HOST:
|
||||
tests.append('snapshot/win/end_to_end_test.py')
|
||||
|
||||
if args.test:
|
||||
for t in args.test:
|
||||
if t not in tests:
|
||||
print('Unrecognized test:', t, file=sys.stderr)
|
||||
return 3
|
||||
tests = args.test
|
||||
|
||||
for test in tests:
|
||||
print '-' * 80
|
||||
print test
|
||||
print '-' * 80
|
||||
subprocess.check_call(os.path.join(binary_dir, test))
|
||||
|
||||
if sys.platform == 'win32':
|
||||
script = 'snapshot/win/end_to_end_test.py'
|
||||
print '-' * 80
|
||||
print script
|
||||
print '-' * 80
|
||||
subprocess.check_call(
|
||||
[sys.executable, os.path.join(crashpad_dir, script), binary_dir])
|
||||
print('-' * 80)
|
||||
print(test)
|
||||
print('-' * 80)
|
||||
if test.endswith('.py'):
|
||||
subprocess.check_call(
|
||||
[sys.executable, os.path.join(CRASHPAD_DIR, test), args.binary_dir])
|
||||
else:
|
||||
extra_command_line = []
|
||||
if args.gtest_filter:
|
||||
extra_command_line.append('--gtest_filter=' + args.gtest_filter)
|
||||
if is_android:
|
||||
_RunOnAndroidTarget(args.binary_dir, test, android_device,
|
||||
extra_command_line)
|
||||
elif is_fuchsia:
|
||||
_RunOnFuchsiaTarget(args.binary_dir, test, zircon_nodename,
|
||||
extra_command_line)
|
||||
else:
|
||||
subprocess.check_call([os.path.join(args.binary_dir, test)] +
|
||||
extra_command_line)
|
||||
|
||||
return 0
|
||||
|
||||
|
26
build/test.gni
Normal file
26
build/test.gni
Normal file
@ -0,0 +1,26 @@
|
||||
# Copyright 2017 The Crashpad Authors. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import("crashpad_buildconfig.gni")
|
||||
|
||||
if (crashpad_is_in_chromium) {
|
||||
import("//testing/test.gni")
|
||||
} else {
|
||||
template("test") {
|
||||
executable(target_name) {
|
||||
testonly = true
|
||||
forward_variables_from(invoker, "*")
|
||||
}
|
||||
}
|
||||
}
|
141
client/BUILD.gn
Normal file
141
client/BUILD.gn
Normal file
@ -0,0 +1,141 @@
|
||||
# Copyright 2015 The Crashpad Authors. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import("../build/crashpad_buildconfig.gni")
|
||||
|
||||
static_library("client") {
|
||||
sources = [
|
||||
"annotation.cc",
|
||||
"annotation.h",
|
||||
"annotation_list.cc",
|
||||
"annotation_list.h",
|
||||
"crash_report_database.cc",
|
||||
"crash_report_database.h",
|
||||
"crashpad_client.h",
|
||||
"crashpad_info.cc",
|
||||
"crashpad_info.h",
|
||||
"prune_crash_reports.cc",
|
||||
"prune_crash_reports.h",
|
||||
"settings.cc",
|
||||
"settings.h",
|
||||
"simple_address_range_bag.h",
|
||||
"simple_string_dictionary.h",
|
||||
"simulate_crash.h",
|
||||
]
|
||||
|
||||
if (crashpad_is_mac) {
|
||||
sources += [
|
||||
"crash_report_database_mac.mm",
|
||||
"crashpad_client_mac.cc",
|
||||
"simulate_crash_mac.cc",
|
||||
"simulate_crash_mac.h",
|
||||
]
|
||||
}
|
||||
|
||||
if (crashpad_is_linux || crashpad_is_android) {
|
||||
set_sources_assignment_filter([])
|
||||
sources += [
|
||||
"crashpad_client_linux.cc",
|
||||
"simulate_crash_linux.h",
|
||||
]
|
||||
}
|
||||
|
||||
if (crashpad_is_linux || crashpad_is_android || crashpad_is_fuchsia) {
|
||||
sources += [
|
||||
"client_argv_handling.cc",
|
||||
"client_argv_handling.h",
|
||||
"crashpad_info_note.S",
|
||||
]
|
||||
}
|
||||
|
||||
if (crashpad_is_win) {
|
||||
sources += [
|
||||
"crash_report_database_win.cc",
|
||||
"crashpad_client_win.cc",
|
||||
"simulate_crash_win.h",
|
||||
]
|
||||
}
|
||||
|
||||
if (crashpad_is_fuchsia) {
|
||||
sources += [ "crashpad_client_fuchsia.cc" ]
|
||||
}
|
||||
|
||||
if (crashpad_is_linux || crashpad_is_android || crashpad_is_fuchsia) {
|
||||
sources += [ "crash_report_database_generic.cc" ]
|
||||
}
|
||||
|
||||
public_configs = [ "..:crashpad_config" ]
|
||||
|
||||
deps = [
|
||||
"../compat",
|
||||
"../third_party/mini_chromium:base",
|
||||
"../util",
|
||||
]
|
||||
|
||||
if (crashpad_is_win) {
|
||||
libs = [ "rpcrt4.lib" ]
|
||||
cflags = [ "/wd4201" ] # nonstandard extension used : nameless struct/union
|
||||
}
|
||||
|
||||
if (crashpad_is_fuchsia && crashpad_is_in_fuchsia) {
|
||||
deps += [
|
||||
"//zircon/public/lib/fdio",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
source_set("client_test") {
|
||||
testonly = true
|
||||
|
||||
sources = [
|
||||
"annotation_list_test.cc",
|
||||
"annotation_test.cc",
|
||||
"crash_report_database_test.cc",
|
||||
"prune_crash_reports_test.cc",
|
||||
"settings_test.cc",
|
||||
"simple_address_range_bag_test.cc",
|
||||
"simple_string_dictionary_test.cc",
|
||||
]
|
||||
|
||||
if (crashpad_is_mac) {
|
||||
sources += [ "simulate_crash_mac_test.cc" ]
|
||||
}
|
||||
|
||||
if (crashpad_is_win) {
|
||||
sources += [ "crashpad_client_win_test.cc" ]
|
||||
}
|
||||
|
||||
if (crashpad_is_linux || crashpad_is_android) {
|
||||
sources += [ "crashpad_client_linux_test.cc" ]
|
||||
}
|
||||
|
||||
deps = [
|
||||
":client",
|
||||
"../compat",
|
||||
"../snapshot",
|
||||
"../test",
|
||||
"../third_party/gtest:gmock",
|
||||
"../third_party/gtest:gtest",
|
||||
"../third_party/mini_chromium:base",
|
||||
"../util",
|
||||
]
|
||||
|
||||
data_deps = [
|
||||
"../handler:crashpad_handler",
|
||||
]
|
||||
|
||||
if (crashpad_is_win) {
|
||||
data_deps += [ "../handler:crashpad_handler_console" ]
|
||||
}
|
||||
}
|
@ -30,7 +30,9 @@ constexpr size_t Annotation::kValueMaxSize;
|
||||
void Annotation::SetSize(ValueSizeType size) {
|
||||
DCHECK_LT(size, kValueMaxSize);
|
||||
size_ = size;
|
||||
AnnotationList::Get()->Add(this);
|
||||
// Use Register() instead of Get() in case the calling module has not
|
||||
// explicitly initialized the annotation list, to avoid crashing.
|
||||
AnnotationList::Register()->Add(this);
|
||||
}
|
||||
|
||||
void Annotation::Clear() {
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include "base/logging.h"
|
||||
#include "base/macros.h"
|
||||
#include "base/numerics/safe_conversions.h"
|
||||
#include "base/strings/string_piece.h"
|
||||
#include "build/build_config.h"
|
||||
|
||||
namespace crashpad {
|
||||
@ -71,7 +72,7 @@ class Annotation {
|
||||
static constexpr size_t kNameMaxLength = 64;
|
||||
|
||||
//! \brief The maximum size of an annotation’s value, in bytes.
|
||||
static constexpr size_t kValueMaxSize = 2048;
|
||||
static constexpr size_t kValueMaxSize = 5 * 4096;
|
||||
|
||||
//! \brief The type used for \a SetSize().
|
||||
using ValueSizeType = uint32_t;
|
||||
@ -195,12 +196,36 @@ class Annotation {
|
||||
template <Annotation::ValueSizeType MaxSize>
|
||||
class StringAnnotation : public Annotation {
|
||||
public:
|
||||
//! \brief A constructor tag that enables braced initialization in C arrays.
|
||||
//!
|
||||
//! \sa StringAnnotation()
|
||||
enum class Tag { kArray };
|
||||
|
||||
//! \brief Constructs a new StringAnnotation with the given \a name.
|
||||
//!
|
||||
//! \param[in] name The Annotation name.
|
||||
constexpr explicit StringAnnotation(const char name[])
|
||||
: Annotation(Type::kString, name, value_), value_() {}
|
||||
|
||||
//! \brief Constructs a new StringAnnotation with the given \a name.
|
||||
//!
|
||||
//! This constructor takes the ArrayInitializerTag for use when
|
||||
//! initializing a C array of annotations. The main constructor is
|
||||
//! explicit and cannot be brace-initialized. As an example:
|
||||
//!
|
||||
//! \code
|
||||
//! static crashpad::StringAnnotation<32> annotations[] = {
|
||||
//! {"name-1", crashpad::StringAnnotation<32>::Tag::kArray},
|
||||
//! {"name-2", crashpad::StringAnnotation<32>::Tag::kArray},
|
||||
//! {"name-3", crashpad::StringAnnotation<32>::Tag::kArray},
|
||||
//! };
|
||||
//! \endcode
|
||||
//!
|
||||
//! \param[in] name The Annotation name.
|
||||
//! \param[in] tag A constructor tag.
|
||||
constexpr StringAnnotation(const char name[], Tag tag)
|
||||
: StringAnnotation(name) {}
|
||||
|
||||
//! \brief Sets the Annotation's string value.
|
||||
//!
|
||||
//! \param[in] value The `NUL`-terminated C-string value.
|
||||
@ -210,6 +235,22 @@ class StringAnnotation : public Annotation {
|
||||
std::min(MaxSize, base::saturated_cast<ValueSizeType>(strlen(value))));
|
||||
}
|
||||
|
||||
//! \brief Sets the Annotation's string value.
|
||||
//!
|
||||
//! \param[in] string The string value.
|
||||
void Set(base::StringPiece string) {
|
||||
Annotation::ValueSizeType size =
|
||||
std::min(MaxSize, base::saturated_cast<ValueSizeType>(string.size()));
|
||||
memcpy(value_, string.data(), size);
|
||||
// Check for no embedded `NUL` characters.
|
||||
DCHECK(!memchr(value_, '\0', size)) << "embedded NUL";
|
||||
SetSize(size);
|
||||
}
|
||||
|
||||
const base::StringPiece value() const {
|
||||
return base::StringPiece(value_, size());
|
||||
}
|
||||
|
||||
private:
|
||||
// This value is not `NUL`-terminated, since the size is stored by the base
|
||||
// annotation.
|
||||
|
@ -14,11 +14,13 @@
|
||||
|
||||
#include "client/annotation.h"
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
#include "client/annotation_list.h"
|
||||
#include "client/crashpad_info.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "test/gtest_death.h"
|
||||
|
||||
namespace crashpad {
|
||||
namespace test {
|
||||
@ -81,14 +83,13 @@ TEST_F(Annotation, Basics) {
|
||||
|
||||
TEST_F(Annotation, StringType) {
|
||||
crashpad::StringAnnotation<5> annotation("name");
|
||||
const char* value_ptr = static_cast<const char*>(annotation.value());
|
||||
|
||||
EXPECT_FALSE(annotation.is_set());
|
||||
|
||||
EXPECT_EQ(crashpad::Annotation::Type::kString, annotation.type());
|
||||
EXPECT_EQ(0u, annotation.size());
|
||||
EXPECT_EQ(std::string("name"), annotation.name());
|
||||
EXPECT_EQ(0u, strlen(value_ptr));
|
||||
EXPECT_EQ(0u, annotation.value().size());
|
||||
|
||||
annotation.Set("test");
|
||||
|
||||
@ -96,17 +97,39 @@ TEST_F(Annotation, StringType) {
|
||||
EXPECT_EQ(1u, AnnotationsCount());
|
||||
|
||||
EXPECT_EQ(4u, annotation.size());
|
||||
EXPECT_EQ(std::string("test"), value_ptr);
|
||||
EXPECT_EQ("test", annotation.value());
|
||||
|
||||
annotation.Set("loooooooooooong");
|
||||
annotation.Set(std::string("loooooooooooong"));
|
||||
|
||||
EXPECT_TRUE(annotation.is_set());
|
||||
EXPECT_EQ(1u, AnnotationsCount());
|
||||
|
||||
EXPECT_EQ(5u, annotation.size());
|
||||
EXPECT_EQ(std::string("loooo"), std::string(value_ptr, annotation.size()));
|
||||
EXPECT_EQ("loooo", annotation.value());
|
||||
}
|
||||
|
||||
TEST(StringAnnotation, ArrayOfString) {
|
||||
static crashpad::StringAnnotation<4> annotations[] = {
|
||||
{"test-1", crashpad::StringAnnotation<4>::Tag::kArray},
|
||||
{"test-2", crashpad::StringAnnotation<4>::Tag::kArray},
|
||||
{"test-3", crashpad::StringAnnotation<4>::Tag::kArray},
|
||||
{"test-4", crashpad::StringAnnotation<4>::Tag::kArray},
|
||||
};
|
||||
|
||||
for (auto& annotation : annotations) {
|
||||
EXPECT_FALSE(annotation.is_set());
|
||||
}
|
||||
}
|
||||
|
||||
#if DCHECK_IS_ON()
|
||||
|
||||
TEST(AnnotationDeathTest, EmbeddedNUL) {
|
||||
crashpad::StringAnnotation<5> annotation("name");
|
||||
EXPECT_DEATH_CHECK(annotation.Set(std::string("te\0st", 5)), "embedded NUL");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
} // namespace test
|
||||
} // namespace crashpad
|
||||
|
@ -1,48 +0,0 @@
|
||||
// Copyright 2014 The Crashpad Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef CRASHPAD_CLIENT_CAPTURE_CONTEXT_MAC_H_
|
||||
#define CRASHPAD_CLIENT_CAPTURE_CONTEXT_MAC_H_
|
||||
|
||||
#include <mach/mach.h>
|
||||
|
||||
#include "build/build_config.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
#if defined(ARCH_CPU_X86_FAMILY)
|
||||
using NativeCPUContext = x86_thread_state;
|
||||
#endif
|
||||
|
||||
//! \brief Saves the CPU context.
|
||||
//!
|
||||
//! The CPU context will be captured as accurately and completely as possible,
|
||||
//! containing an atomic snapshot at the point of this function’s return. This
|
||||
//! function does not modify any registers.
|
||||
//!
|
||||
//! \param[out] cpu_context The structure to store the context in.
|
||||
//!
|
||||
//! \note On x86_64, the value for `%%rdi` will be populated with the address of
|
||||
//! this function’s argument, as mandated by the ABI. If the value of
|
||||
//! `%%rdi` prior to calling this function is needed, it must be obtained
|
||||
//! separately prior to calling this function. For example:
|
||||
//! \code
|
||||
//! uint64_t rdi;
|
||||
//! asm("movq %%rdi, %0" : "=m"(rdi));
|
||||
//! \endcode
|
||||
void CaptureContext(NativeCPUContext* cpu_context);
|
||||
|
||||
} // namespace crashpad
|
||||
|
||||
#endif // CRASHPAD_CLIENT_CAPTURE_CONTEXT_MAC_H_
|
@ -1,158 +0,0 @@
|
||||
// Copyright 2014 The Crashpad Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "client/capture_context_mac.h"
|
||||
|
||||
#include <mach/mach.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "build/build_config.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "util/misc/address_sanitizer.h"
|
||||
#include "util/misc/implicit_cast.h"
|
||||
|
||||
namespace crashpad {
|
||||
namespace test {
|
||||
namespace {
|
||||
|
||||
// If the context structure has fields that tell whether it’s valid, such as
|
||||
// magic numbers or size fields, sanity-checks those fields for validity with
|
||||
// fatal gtest assertions. For other fields, where it’s possible to reason about
|
||||
// their validity based solely on their contents, sanity-checks via nonfatal
|
||||
// gtest assertions.
|
||||
void SanityCheckContext(const NativeCPUContext& context) {
|
||||
#if defined(ARCH_CPU_X86)
|
||||
ASSERT_EQ(implicit_cast<thread_state_flavor_t>(context.tsh.flavor),
|
||||
implicit_cast<thread_state_flavor_t>(x86_THREAD_STATE32));
|
||||
ASSERT_EQ(implicit_cast<uint32_t>(context.tsh.count),
|
||||
implicit_cast<uint32_t>(x86_THREAD_STATE32_COUNT));
|
||||
#elif defined(ARCH_CPU_X86_64)
|
||||
ASSERT_EQ(implicit_cast<thread_state_flavor_t>(context.tsh.flavor),
|
||||
implicit_cast<thread_state_flavor_t>(x86_THREAD_STATE64));
|
||||
ASSERT_EQ(implicit_cast<uint32_t>(context.tsh.count),
|
||||
implicit_cast<uint32_t>(x86_THREAD_STATE64_COUNT));
|
||||
#endif
|
||||
|
||||
#if defined(ARCH_CPU_X86_FAMILY)
|
||||
// The segment registers are only capable of storing 16-bit quantities, but
|
||||
// the context structure provides native integer-width fields for them. Ensure
|
||||
// that the high bits are all clear.
|
||||
//
|
||||
// Many bit positions in the flags register are reserved and will always read
|
||||
// a known value. Most reserved bits are always 0, but bit 1 is always 1.
|
||||
// Check that the reserved bits are all set to their expected values. Note
|
||||
// that the set of reserved bits may be relaxed over time with newer CPUs, and
|
||||
// that this test may need to be changed to reflect these developments. The
|
||||
// current set of reserved bits are 1, 3, 5, 15, and 22 and higher. See Intel
|
||||
// Software Developer’s Manual, Volume 1: Basic Architecture (253665-051),
|
||||
// 3.4.3 “EFLAGS Register”, and AMD Architecture Programmer’s Manual, Volume
|
||||
// 2: System Programming (24593-3.24), 3.1.6 “RFLAGS Register”.
|
||||
#if defined(ARCH_CPU_X86)
|
||||
EXPECT_EQ(context.uts.ts32.__cs & ~0xffff, 0u);
|
||||
EXPECT_EQ(context.uts.ts32.__ds & ~0xffff, 0u);
|
||||
EXPECT_EQ(context.uts.ts32.__es & ~0xffff, 0u);
|
||||
EXPECT_EQ(context.uts.ts32.__fs & ~0xffff, 0u);
|
||||
EXPECT_EQ(context.uts.ts32.__gs & ~0xffff, 0u);
|
||||
EXPECT_EQ(context.uts.ts32.__ss & ~0xffff, 0u);
|
||||
EXPECT_EQ(context.uts.ts32.__eflags & 0xffc0802a, 2u);
|
||||
#elif defined(ARCH_CPU_X86_64)
|
||||
EXPECT_EQ(context.uts.ts64.__cs & ~UINT64_C(0xffff), 0u);
|
||||
EXPECT_EQ(context.uts.ts64.__fs & ~UINT64_C(0xffff), 0u);
|
||||
EXPECT_EQ(context.uts.ts64.__gs & ~UINT64_C(0xffff), 0u);
|
||||
EXPECT_EQ(context.uts.ts64.__rflags & UINT64_C(0xffffffffffc0802a), 2u);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
// A CPU-independent function to return the program counter.
|
||||
uintptr_t ProgramCounterFromContext(const NativeCPUContext& context) {
|
||||
#if defined(ARCH_CPU_X86)
|
||||
return context.uts.ts32.__eip;
|
||||
#elif defined(ARCH_CPU_X86_64)
|
||||
return context.uts.ts64.__rip;
|
||||
#endif
|
||||
}
|
||||
|
||||
// A CPU-independent function to return the stack pointer.
|
||||
uintptr_t StackPointerFromContext(const NativeCPUContext& context) {
|
||||
#if defined(ARCH_CPU_X86)
|
||||
return context.uts.ts32.__esp;
|
||||
#elif defined(ARCH_CPU_X86_64)
|
||||
return context.uts.ts64.__rsp;
|
||||
#endif
|
||||
}
|
||||
|
||||
void TestCaptureContext() {
|
||||
NativeCPUContext context_1;
|
||||
CaptureContext(&context_1);
|
||||
|
||||
{
|
||||
SCOPED_TRACE("context_1");
|
||||
ASSERT_NO_FATAL_FAILURE(SanityCheckContext(context_1));
|
||||
}
|
||||
|
||||
// The program counter reference value is this function’s address. The
|
||||
// captured program counter should be slightly greater than or equal to the
|
||||
// reference program counter.
|
||||
uintptr_t pc = ProgramCounterFromContext(context_1);
|
||||
|
||||
#if !defined(ADDRESS_SANITIZER)
|
||||
// AddressSanitizer can cause enough code bloat that the “nearby” check would
|
||||
// likely fail.
|
||||
const uintptr_t kReferencePC =
|
||||
reinterpret_cast<uintptr_t>(TestCaptureContext);
|
||||
EXPECT_LT(pc - kReferencePC, 64u);
|
||||
#endif // !defined(ADDRESS_SANITIZER)
|
||||
|
||||
// Declare sp and context_2 here because all local variables need to be
|
||||
// declared before computing the stack pointer reference value, so that the
|
||||
// reference value can be the lowest value possible.
|
||||
uintptr_t sp;
|
||||
NativeCPUContext context_2;
|
||||
|
||||
// The stack pointer reference value is the lowest address of a local variable
|
||||
// in this function. The captured program counter will be slightly less than
|
||||
// or equal to the reference stack pointer.
|
||||
const uintptr_t kReferenceSP =
|
||||
std::min(std::min(reinterpret_cast<uintptr_t>(&context_1),
|
||||
reinterpret_cast<uintptr_t>(&context_2)),
|
||||
std::min(reinterpret_cast<uintptr_t>(&pc),
|
||||
reinterpret_cast<uintptr_t>(&sp)));
|
||||
sp = StackPointerFromContext(context_1);
|
||||
EXPECT_LT(kReferenceSP - sp, 512u);
|
||||
|
||||
// Capture the context again, expecting that the stack pointer stays the same
|
||||
// and the program counter increases. Strictly speaking, there’s no guarantee
|
||||
// that these conditions will hold, although they do for known compilers even
|
||||
// under typical optimization.
|
||||
CaptureContext(&context_2);
|
||||
|
||||
{
|
||||
SCOPED_TRACE("context_2");
|
||||
ASSERT_NO_FATAL_FAILURE(SanityCheckContext(context_2));
|
||||
}
|
||||
|
||||
EXPECT_EQ(StackPointerFromContext(context_2), sp);
|
||||
EXPECT_GT(ProgramCounterFromContext(context_2), pc);
|
||||
}
|
||||
|
||||
TEST(CaptureContextMac, CaptureContext) {
|
||||
ASSERT_NO_FATAL_FAILURE(TestCaptureContext());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace test
|
||||
} // namespace crashpad
|
@ -33,13 +33,12 @@
|
||||
'annotation.h',
|
||||
'annotation_list.cc',
|
||||
'annotation_list.h',
|
||||
'capture_context_mac.S',
|
||||
'capture_context_mac.h',
|
||||
'crash_report_database.cc',
|
||||
'crash_report_database.h',
|
||||
'crash_report_database_mac.mm',
|
||||
'crash_report_database_win.cc',
|
||||
'crashpad_client.h',
|
||||
'crashpad_client_linux.cc',
|
||||
'crashpad_client_mac.cc',
|
||||
'crashpad_client_win.cc',
|
||||
'crashpad_info.cc',
|
||||
@ -51,6 +50,7 @@
|
||||
'simple_string_dictionary.h',
|
||||
'simple_address_range_bag.h',
|
||||
'simulate_crash.h',
|
||||
'simulate_crash_linux.h',
|
||||
'simulate_crash_mac.cc',
|
||||
'simulate_crash_mac.h',
|
||||
'simulate_crash_win.h',
|
||||
@ -63,9 +63,20 @@
|
||||
],
|
||||
},
|
||||
}],
|
||||
['OS!="mac"', {
|
||||
'sources!': [
|
||||
'capture_context_mac.S',
|
||||
['OS=="linux" or OS=="android"', {
|
||||
'sources': [
|
||||
'client_argv_handling.cc',
|
||||
'client_argv_handling.h',
|
||||
'crashpad_info_note.S',
|
||||
'crash_report_database_generic.cc',
|
||||
],
|
||||
}],
|
||||
],
|
||||
'target_conditions': [
|
||||
['OS=="android"', {
|
||||
'sources/': [
|
||||
['include', '^crashpad_client_linux\\.cc$'],
|
||||
['include', '^simulate_crash_linux\\.h$'],
|
||||
],
|
||||
}],
|
||||
],
|
||||
|
74
client/client_argv_handling.cc
Normal file
74
client/client_argv_handling.cc
Normal file
@ -0,0 +1,74 @@
|
||||
// Copyright 2018 The Crashpad Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "client/client_argv_handling.h"
|
||||
|
||||
#include "base/strings/stringprintf.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
namespace {
|
||||
|
||||
std::string FormatArgumentString(const std::string& name,
|
||||
const std::string& value) {
|
||||
return base::StringPrintf("--%s=%s", name.c_str(), value.c_str());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::vector<std::string> BuildHandlerArgvStrings(
|
||||
const base::FilePath& handler,
|
||||
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_strings(1, handler.value());
|
||||
|
||||
for (const auto& argument : arguments) {
|
||||
argv_strings.push_back(argument);
|
||||
}
|
||||
|
||||
if (!database.empty()) {
|
||||
argv_strings.push_back(FormatArgumentString("database", database.value()));
|
||||
}
|
||||
|
||||
if (!metrics_dir.empty()) {
|
||||
argv_strings.push_back(
|
||||
FormatArgumentString("metrics-dir", metrics_dir.value()));
|
||||
}
|
||||
|
||||
if (!url.empty()) {
|
||||
argv_strings.push_back(FormatArgumentString("url", url));
|
||||
}
|
||||
|
||||
for (const auto& kv : annotations) {
|
||||
argv_strings.push_back(
|
||||
FormatArgumentString("annotation", kv.first + '=' + kv.second));
|
||||
}
|
||||
|
||||
return argv_strings;
|
||||
}
|
||||
|
||||
void ConvertArgvStrings(const std::vector<std::string>& argv_strings,
|
||||
std::vector<const char*>* argv) {
|
||||
argv->clear();
|
||||
argv->reserve(argv_strings.size() + 1);
|
||||
for (const auto& arg : argv_strings) {
|
||||
argv->push_back(arg.c_str());
|
||||
}
|
||||
argv->push_back(nullptr);
|
||||
}
|
||||
|
||||
} // namespace crashpad
|
51
client/client_argv_handling.h
Normal file
51
client/client_argv_handling.h
Normal file
@ -0,0 +1,51 @@
|
||||
// Copyright 2018 The Crashpad Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef CRASHPAD_CLIENT_CLIENT_ARGV_HANDLING_H_
|
||||
#define CRASHPAD_CLIENT_CLIENT_ARGV_HANDLING_H_
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/files/file_path.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
//! \brief Builds a vector of arguments suitable for invoking a handler process
|
||||
//! based on arguments passed to StartHandler-type().
|
||||
//!
|
||||
//! See StartHandlerAtCrash() for documentation on the input arguments.
|
||||
//!
|
||||
//! \return A vector of arguments suitable for starting the handler with.
|
||||
std::vector<std::string> BuildHandlerArgvStrings(
|
||||
const base::FilePath& handler,
|
||||
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 Flattens a string vector into a const char* vector suitable for use
|
||||
//! in an exec() call.
|
||||
//!
|
||||
//! \param[in] argv_strings Arguments to be passed to child process, typically
|
||||
//! created by BuildHandlerArgvStrings().
|
||||
//! \param[out] argv argv suitable for starting the child process.
|
||||
void ConvertArgvStrings(const std::vector<std::string>& argv_strings,
|
||||
std::vector<const char*>* argv);
|
||||
|
||||
} // namespace crashpad
|
||||
|
||||
#endif // CRASHPAD_CLIENT_CLIENT_ARGV_HANDLING_H_
|
@ -24,6 +24,7 @@
|
||||
'client.gyp:crashpad_client',
|
||||
'../compat/compat.gyp:crashpad_compat',
|
||||
'../handler/handler.gyp:crashpad_handler',
|
||||
'../snapshot/snapshot.gyp:crashpad_snapshot',
|
||||
'../test/test.gyp:crashpad_gmock_main',
|
||||
'../test/test.gyp:crashpad_test',
|
||||
'../third_party/gtest/gmock.gyp:gmock',
|
||||
@ -37,9 +38,9 @@
|
||||
'sources': [
|
||||
'annotation_test.cc',
|
||||
'annotation_list_test.cc',
|
||||
'capture_context_mac_test.cc',
|
||||
'crash_report_database_test.cc',
|
||||
'crashpad_client_win_test.cc',
|
||||
'crashpad_client_linux_test.cc',
|
||||
'prune_crash_reports_test.cc',
|
||||
'settings_test.cc',
|
||||
'simple_address_range_bag_test.cc',
|
||||
@ -53,6 +54,13 @@
|
||||
],
|
||||
}],
|
||||
],
|
||||
'target_conditions': [
|
||||
['OS=="android"', {
|
||||
'sources/': [
|
||||
['include', '^crashpad_client_linux_test\\.cc$'],
|
||||
],
|
||||
}],
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
@ -14,6 +14,9 @@
|
||||
|
||||
#include "client/crash_report_database.h"
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "build/build_config.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
CrashReportDatabase::Report::Report()
|
||||
@ -26,22 +29,68 @@ CrashReportDatabase::Report::Report()
|
||||
upload_attempts(0),
|
||||
upload_explicitly_requested(false) {}
|
||||
|
||||
CrashReportDatabase::CallErrorWritingCrashReport::CallErrorWritingCrashReport(
|
||||
CrashReportDatabase::NewReport::NewReport()
|
||||
: writer_(std::make_unique<FileWriter>()),
|
||||
file_remover_(),
|
||||
attachment_writers_(),
|
||||
attachment_removers_(),
|
||||
uuid_(),
|
||||
database_() {}
|
||||
|
||||
CrashReportDatabase::NewReport::~NewReport() = default;
|
||||
|
||||
bool CrashReportDatabase::NewReport::Initialize(
|
||||
CrashReportDatabase* database,
|
||||
NewReport* new_report)
|
||||
: database_(database),
|
||||
new_report_(new_report) {
|
||||
const base::FilePath& directory,
|
||||
const base::FilePath::StringType& extension) {
|
||||
database_ = database;
|
||||
|
||||
if (!uuid_.InitializeWithNew()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(OS_WIN)
|
||||
const std::wstring uuid_string = uuid_.ToString16();
|
||||
#else
|
||||
const std::string uuid_string = uuid_.ToString();
|
||||
#endif
|
||||
|
||||
const base::FilePath path = directory.Append(uuid_string + extension);
|
||||
if (!writer_->Open(
|
||||
path, FileWriteMode::kCreateOrFail, FilePermissions::kOwnerOnly)) {
|
||||
return false;
|
||||
}
|
||||
file_remover_.reset(path);
|
||||
return true;
|
||||
}
|
||||
|
||||
CrashReportDatabase::CallErrorWritingCrashReport::
|
||||
~CallErrorWritingCrashReport() {
|
||||
if (new_report_) {
|
||||
database_->ErrorWritingCrashReport(new_report_);
|
||||
CrashReportDatabase::UploadReport::UploadReport()
|
||||
: Report(),
|
||||
reader_(std::make_unique<FileReader>()),
|
||||
database_(nullptr),
|
||||
attachment_readers_(),
|
||||
attachment_map_() {}
|
||||
|
||||
CrashReportDatabase::UploadReport::~UploadReport() {
|
||||
if (database_) {
|
||||
database_->RecordUploadAttempt(this, false, std::string());
|
||||
}
|
||||
}
|
||||
|
||||
void CrashReportDatabase::CallErrorWritingCrashReport::Disarm() {
|
||||
new_report_ = nullptr;
|
||||
bool CrashReportDatabase::UploadReport::Initialize(const base::FilePath path,
|
||||
CrashReportDatabase* db) {
|
||||
database_ = db;
|
||||
InitializeAttachments();
|
||||
return reader_->Open(path);
|
||||
}
|
||||
|
||||
CrashReportDatabase::OperationStatus CrashReportDatabase::RecordUploadComplete(
|
||||
std::unique_ptr<const UploadReport> report_in,
|
||||
const std::string& id) {
|
||||
UploadReport* report = const_cast<UploadReport*>(report_in.get());
|
||||
|
||||
report->database_ = nullptr;
|
||||
return RecordUploadAttempt(report, true, id);
|
||||
}
|
||||
|
||||
} // namespace crashpad
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
#include <time.h>
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@ -24,6 +25,9 @@
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/macros.h"
|
||||
#include "util/file/file_io.h"
|
||||
#include "util/file/file_reader.h"
|
||||
#include "util/file/file_writer.h"
|
||||
#include "util/file/scoped_remove_file.h"
|
||||
#include "util/misc/metrics.h"
|
||||
#include "util/misc/uuid.h"
|
||||
|
||||
@ -47,7 +51,7 @@ class Settings;
|
||||
//! processed, or it was has been brought back from 'Completed' state by
|
||||
//! user request.
|
||||
//! 3. Completed: The report has been locally processed, either by uploading
|
||||
//! it to a collection server and calling RecordUploadAttempt(), or by
|
||||
//! it to a collection server and calling RecordUploadComplete(), or by
|
||||
//! calling SkipReportUpload().
|
||||
class CrashReportDatabase {
|
||||
public:
|
||||
@ -98,44 +102,84 @@ class CrashReportDatabase {
|
||||
|
||||
//! \brief A crash report that is in the process of being written.
|
||||
//!
|
||||
//! An instance of this struct should be created via PrepareNewCrashReport()
|
||||
//! and destroyed with FinishedWritingCrashReport().
|
||||
struct NewReport {
|
||||
//! The file handle to which the report should be written.
|
||||
FileHandle handle;
|
||||
//! An instance of this class should be created via PrepareNewCrashReport().
|
||||
class NewReport {
|
||||
public:
|
||||
NewReport();
|
||||
~NewReport();
|
||||
|
||||
//! An open FileWriter with which to write the report.
|
||||
FileWriter* Writer() const { return writer_.get(); }
|
||||
|
||||
//! A unique identifier by which this report will always be known to the
|
||||
//! database.
|
||||
UUID uuid;
|
||||
const UUID& ReportID() const { return uuid_; }
|
||||
|
||||
//! The path to the crash report being written.
|
||||
base::FilePath path;
|
||||
};
|
||||
|
||||
//! \brief A scoper to cleanly handle the interface requirement imposed by
|
||||
//! PrepareNewCrashReport().
|
||||
//!
|
||||
//! Calls ErrorWritingCrashReport() upon destruction unless disarmed by
|
||||
//! calling Disarm(). Armed upon construction.
|
||||
class CallErrorWritingCrashReport {
|
||||
public:
|
||||
//! \brief Arms the object to call ErrorWritingCrashReport() on \a database
|
||||
//! with an argument of \a new_report on destruction.
|
||||
CallErrorWritingCrashReport(CrashReportDatabase* database,
|
||||
NewReport* new_report);
|
||||
|
||||
//! \brief Calls ErrorWritingCrashReport() if the object is armed.
|
||||
~CallErrorWritingCrashReport();
|
||||
|
||||
//! \brief Disarms the object so that CallErrorWritingCrashReport() will not
|
||||
//! be called upon destruction.
|
||||
void Disarm();
|
||||
//! \brief Adds an attachment to the report.
|
||||
//!
|
||||
//! \note This function is not yet implemented on macOS or Windows.
|
||||
//!
|
||||
//! \param[in] name The key and name for the attachment, which will be
|
||||
//! included in the http upload. The attachment will not appear in the
|
||||
//! minidump report. \a name should only use characters from the set
|
||||
//! `[a-zA-Z0-9._-]`.
|
||||
//! \return A FileWriter that the caller should use to write the contents of
|
||||
//! the attachment, or `nullptr` on failure with an error logged.
|
||||
FileWriter* AddAttachment(const std::string& name);
|
||||
|
||||
private:
|
||||
CrashReportDatabase* database_; // weak
|
||||
NewReport* new_report_; // weak
|
||||
friend class CrashReportDatabaseGeneric;
|
||||
friend class CrashReportDatabaseMac;
|
||||
friend class CrashReportDatabaseWin;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(CallErrorWritingCrashReport);
|
||||
bool Initialize(CrashReportDatabase* database,
|
||||
const base::FilePath& directory,
|
||||
const base::FilePath::StringType& extension);
|
||||
|
||||
std::unique_ptr<FileWriter> writer_;
|
||||
ScopedRemoveFile file_remover_;
|
||||
std::vector<std::unique_ptr<FileWriter>> attachment_writers_;
|
||||
std::vector<ScopedRemoveFile> attachment_removers_;
|
||||
UUID uuid_;
|
||||
CrashReportDatabase* database_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(NewReport);
|
||||
};
|
||||
|
||||
//! \brief A crash report that is in the process of being uploaded.
|
||||
//!
|
||||
//! An instance of this class should be created via GetReportForUploading().
|
||||
class UploadReport : public Report {
|
||||
public:
|
||||
UploadReport();
|
||||
virtual ~UploadReport();
|
||||
|
||||
//! \brief An open FileReader with which to read the report.
|
||||
FileReader* Reader() const { return reader_.get(); }
|
||||
|
||||
//! \brief Obtains a mapping of names to file readers for any attachments
|
||||
//! for the report.
|
||||
//!
|
||||
//! This is not implemented on macOS or Windows.
|
||||
std::map<std::string, FileReader*> GetAttachments() const {
|
||||
return attachment_map_;
|
||||
};
|
||||
|
||||
private:
|
||||
friend class CrashReportDatabase;
|
||||
friend class CrashReportDatabaseGeneric;
|
||||
friend class CrashReportDatabaseMac;
|
||||
friend class CrashReportDatabaseWin;
|
||||
|
||||
bool Initialize(const base::FilePath path, CrashReportDatabase* database);
|
||||
void InitializeAttachments();
|
||||
|
||||
std::unique_ptr<FileReader> reader_;
|
||||
CrashReportDatabase* database_;
|
||||
std::vector<std::unique_ptr<FileReader>> attachment_readers_;
|
||||
std::map<std::string, FileReader*> attachment_map_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(UploadReport);
|
||||
};
|
||||
|
||||
//! \brief The result code for operations performed on a database.
|
||||
@ -217,49 +261,31 @@ class CrashReportDatabase {
|
||||
|
||||
//! \brief Creates a record of a new crash report.
|
||||
//!
|
||||
//! Callers can then write the crash report using the file handle provided.
|
||||
//! The caller does not own the new crash report record or its file handle,
|
||||
//! both of which must be explicitly disposed of by calling
|
||||
//! FinishedWritingCrashReport() or ErrorWritingCrashReport().
|
||||
//! Callers should write the crash report using the FileWriter provided.
|
||||
//! Callers should then call FinishedWritingCrashReport() to complete report
|
||||
//! creation. If an error is encountered while writing the crash report, no
|
||||
//! special action needs to be taken. If FinishedWritingCrashReport() is not
|
||||
//! called, the report will be removed from the database when \a report is
|
||||
//! destroyed.
|
||||
//!
|
||||
//! To arrange to call ErrorWritingCrashReport() during any early return, use
|
||||
//! CallErrorWritingCrashReport.
|
||||
//!
|
||||
//! \param[out] report A NewReport object containing a file handle to which
|
||||
//! the crash report data should be written. Only valid if this returns
|
||||
//! #kNoError. The caller must not delete the NewReport object or close
|
||||
//! the file handle within.
|
||||
//! \param[out] report A NewReport object containing a FileWriter with which
|
||||
//! to write the report data. Only valid if this returns #kNoError.
|
||||
//!
|
||||
//! \return The operation status code.
|
||||
virtual OperationStatus PrepareNewCrashReport(NewReport** report) = 0;
|
||||
virtual OperationStatus PrepareNewCrashReport(
|
||||
std::unique_ptr<NewReport>* report) = 0;
|
||||
|
||||
//! \brief Informs the database that a crash report has been written.
|
||||
//!
|
||||
//! After calling this method, the database is permitted to move and rename
|
||||
//! the file at NewReport::path.
|
||||
//! \brief Informs the database that a crash report has been successfully
|
||||
//! written.
|
||||
//!
|
||||
//! \param[in] report A NewReport obtained with PrepareNewCrashReport(). The
|
||||
//! NewReport object and file handle within will be invalidated as part of
|
||||
//! this call.
|
||||
//! NewReport object will be invalidated as part of this call.
|
||||
//! \param[out] uuid The UUID of this crash report.
|
||||
//!
|
||||
//! \return The operation status code.
|
||||
virtual OperationStatus FinishedWritingCrashReport(NewReport* report,
|
||||
UUID* uuid) = 0;
|
||||
|
||||
//! \brief Informs the database that an error occurred while attempting to
|
||||
//! write a crash report, and that any resources associated with it should
|
||||
//! be cleaned up.
|
||||
//!
|
||||
//! After calling this method, the database is permitted to remove the file at
|
||||
//! NewReport::path.
|
||||
//!
|
||||
//! \param[in] report A NewReport obtained with PrepareNewCrashReport(). The
|
||||
//! NewReport object and file handle within will be invalidated as part of
|
||||
//! this call.
|
||||
//!
|
||||
//! \return The operation status code.
|
||||
virtual OperationStatus ErrorWritingCrashReport(NewReport* report) = 0;
|
||||
virtual OperationStatus FinishedWritingCrashReport(
|
||||
std::unique_ptr<NewReport> report,
|
||||
UUID* uuid) = 0;
|
||||
|
||||
//! \brief Returns the crash report record for the unique identifier.
|
||||
//!
|
||||
@ -288,42 +314,38 @@ class CrashReportDatabase {
|
||||
//! \return The operation status code.
|
||||
virtual OperationStatus GetCompletedReports(std::vector<Report>* reports) = 0;
|
||||
|
||||
//! \brief Obtains a report object for uploading to a collection server.
|
||||
//! \brief Obtains and locks a report object for uploading to a collection
|
||||
//! server.
|
||||
//!
|
||||
//! The file at Report::file_path should be uploaded by the caller, and then
|
||||
//! the returned Report object must be disposed of via a call to
|
||||
//! RecordUploadAttempt().
|
||||
//!
|
||||
//! A subsequent call to this method with the same \a uuid is illegal until
|
||||
//! RecordUploadAttempt() has been called.
|
||||
//! Callers should upload the crash report using the FileReader provided.
|
||||
//! Callers should then call RecordUploadComplete() to record a successful
|
||||
//! upload. If RecordUploadComplete() is not called, the upload attempt will
|
||||
//! be recorded as unsuccessful and the report lock released when \a report is
|
||||
//! destroyed.
|
||||
//!
|
||||
//! \param[in] uuid The unique identifier for the crash report record.
|
||||
//! \param[out] report A crash report record for the report to be uploaded.
|
||||
//! The caller does not own this object. Only valid if this returns
|
||||
//! #kNoError.
|
||||
//! Only valid if this returns #kNoError.
|
||||
//!
|
||||
//! \return The operation status code.
|
||||
virtual OperationStatus GetReportForUploading(const UUID& uuid,
|
||||
const Report** report) = 0;
|
||||
virtual OperationStatus GetReportForUploading(
|
||||
const UUID& uuid,
|
||||
std::unique_ptr<const UploadReport>* report) = 0;
|
||||
|
||||
//! \brief Adjusts a crash report record’s metadata to account for an upload
|
||||
//! attempt, and updates the last upload attempt time as returned by
|
||||
//! \brief Records a successful upload for a report and updates the last
|
||||
//! upload attempt time as returned by
|
||||
//! Settings::GetLastUploadAttemptTime().
|
||||
//!
|
||||
//! After calling this method, the database is permitted to move and rename
|
||||
//! the file at Report::file_path.
|
||||
//!
|
||||
//! \param[in] report The report object obtained from
|
||||
//! GetReportForUploading(). This object is invalidated after this call.
|
||||
//! \param[in] successful Whether the upload attempt was successful.
|
||||
//! \param[in] id The identifier assigned to this crash report by the
|
||||
//! collection server. Must be empty if \a successful is `false`; may be
|
||||
//! empty if it is `true`.
|
||||
//! \param[in] report A UploadReport object obtained from
|
||||
//! GetReportForUploading(). The UploadReport object will be invalidated
|
||||
//! and the report unlocked as part of this call.
|
||||
//! \param[in] id The possibly empty identifier assigned to this crash report
|
||||
//! by the collection server.
|
||||
//!
|
||||
//! \return The operation status code.
|
||||
virtual OperationStatus RecordUploadAttempt(const Report* report,
|
||||
bool successful,
|
||||
const std::string& id) = 0;
|
||||
OperationStatus RecordUploadComplete(
|
||||
std::unique_ptr<const UploadReport> report,
|
||||
const std::string& id);
|
||||
|
||||
//! \brief Moves a report from the pending state to the completed state, but
|
||||
//! without the report being uploaded.
|
||||
@ -355,10 +377,37 @@ class CrashReportDatabase {
|
||||
//! \return The operation status code.
|
||||
virtual OperationStatus RequestUpload(const UUID& uuid) = 0;
|
||||
|
||||
//! \brief Cleans the database of expired lockfiles, metadata without report
|
||||
//! files, and report files without metadata.
|
||||
//!
|
||||
//! This method does nothing on the macOS and Windows implementations of the
|
||||
//! database.
|
||||
//!
|
||||
//! \param[in] lockfile_ttl The number of seconds at which lockfiles or new
|
||||
//! report files are considered expired.
|
||||
//! \return The number of reports cleaned.
|
||||
virtual int CleanDatabase(time_t lockfile_ttl) { return 0; }
|
||||
|
||||
protected:
|
||||
CrashReportDatabase() {}
|
||||
|
||||
private:
|
||||
//! \brief Adjusts a crash report record’s metadata to account for an upload
|
||||
//! attempt, and updates the last upload attempt time as returned by
|
||||
//! Settings::GetLastUploadAttemptTime().
|
||||
//!
|
||||
//! \param[in] report The report object obtained from
|
||||
//! GetReportForUploading().
|
||||
//! \param[in] successful Whether the upload attempt was successful.
|
||||
//! \param[in] id The identifier assigned to this crash report by the
|
||||
//! collection server. Must be empty if \a successful is `false`; may be
|
||||
//! empty if it is `true`.
|
||||
//!
|
||||
//! \return The operation status code.
|
||||
virtual OperationStatus RecordUploadAttempt(UploadReport* report,
|
||||
bool successful,
|
||||
const std::string& id) = 0;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(CrashReportDatabase);
|
||||
};
|
||||
|
||||
|
996
client/crash_report_database_generic.cc
Normal file
996
client/crash_report_database_generic.cc
Normal file
@ -0,0 +1,996 @@
|
||||
// Copyright 2018 The Crashpad Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "client/crash_report_database.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "build/build_config.h"
|
||||
#include "client/settings.h"
|
||||
#include "util/file/directory_reader.h"
|
||||
#include "util/file/filesystem.h"
|
||||
#include "util/misc/initialization_state_dcheck.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
namespace {
|
||||
|
||||
base::FilePath ReplaceFinalExtension(
|
||||
const base::FilePath& path,
|
||||
const base::FilePath::StringType extension) {
|
||||
return base::FilePath(path.RemoveFinalExtension().value() + extension);
|
||||
}
|
||||
|
||||
UUID UUIDFromReportPath(const base::FilePath& path) {
|
||||
UUID uuid;
|
||||
uuid.InitializeFromString(path.RemoveFinalExtension().BaseName().value());
|
||||
return uuid;
|
||||
}
|
||||
|
||||
bool AttachmentNameIsOK(const std::string& name) {
|
||||
for (const char c : name) {
|
||||
if (c != '_' && c != '-' && c != '.' && !isalnum(c))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
using OperationStatus = CrashReportDatabase::OperationStatus;
|
||||
|
||||
constexpr base::FilePath::CharType kSettings[] =
|
||||
FILE_PATH_LITERAL("settings.dat");
|
||||
|
||||
constexpr base::FilePath::CharType kCrashReportExtension[] =
|
||||
FILE_PATH_LITERAL(".dmp");
|
||||
constexpr base::FilePath::CharType kMetadataExtension[] =
|
||||
FILE_PATH_LITERAL(".meta");
|
||||
constexpr base::FilePath::CharType kLockExtension[] =
|
||||
FILE_PATH_LITERAL(".lock");
|
||||
|
||||
constexpr base::FilePath::CharType kNewDirectory[] = FILE_PATH_LITERAL("new");
|
||||
constexpr base::FilePath::CharType kPendingDirectory[] =
|
||||
FILE_PATH_LITERAL("pending");
|
||||
constexpr base::FilePath::CharType kCompletedDirectory[] =
|
||||
FILE_PATH_LITERAL("completed");
|
||||
constexpr base::FilePath::CharType kAttachmentsDirectory[] =
|
||||
FILE_PATH_LITERAL("attachments");
|
||||
|
||||
constexpr const base::FilePath::CharType* kReportDirectories[] = {
|
||||
kNewDirectory,
|
||||
kPendingDirectory,
|
||||
kCompletedDirectory,
|
||||
};
|
||||
|
||||
enum {
|
||||
//! \brief Corresponds to uploaded bit of the report state.
|
||||
kAttributeUploaded = 1 << 0,
|
||||
|
||||
//! \brief Corresponds to upload_explicity_requested bit of the report state.
|
||||
kAttributeUploadExplicitlyRequested = 1 << 1,
|
||||
};
|
||||
|
||||
struct ReportMetadata {
|
||||
static constexpr int32_t kVersion = 1;
|
||||
|
||||
int32_t version = kVersion;
|
||||
int32_t upload_attempts = 0;
|
||||
int64_t last_upload_attempt_time = 0;
|
||||
time_t creation_time = 0;
|
||||
uint8_t attributes = 0;
|
||||
};
|
||||
|
||||
// A lock held while using database resources.
|
||||
class ScopedLockFile {
|
||||
public:
|
||||
ScopedLockFile() = default;
|
||||
~ScopedLockFile() = default;
|
||||
|
||||
ScopedLockFile& operator=(ScopedLockFile&& other) {
|
||||
lock_file_.reset(other.lock_file_.release());
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Attempt to acquire a lock for the report at report_path.
|
||||
// Return `true` on success, otherwise `false`.
|
||||
bool ResetAcquire(const base::FilePath& report_path) {
|
||||
lock_file_.reset();
|
||||
|
||||
base::FilePath lock_path(report_path.RemoveFinalExtension().value() +
|
||||
kLockExtension);
|
||||
ScopedFileHandle lock_fd(LoggingOpenFileForWrite(
|
||||
lock_path, FileWriteMode::kCreateOrFail, FilePermissions::kOwnerOnly));
|
||||
if (!lock_fd.is_valid()) {
|
||||
return false;
|
||||
}
|
||||
lock_file_.reset(lock_path);
|
||||
|
||||
time_t timestamp = time(nullptr);
|
||||
if (!LoggingWriteFile(lock_fd.get(), ×tamp, sizeof(timestamp))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns `true` if the lock is held.
|
||||
bool is_valid() const { return lock_file_.is_valid(); }
|
||||
|
||||
// Returns `true` if the lockfile at lock_path has expired.
|
||||
static bool IsExpired(const base::FilePath& lock_path, time_t lockfile_ttl) {
|
||||
time_t now = time(nullptr);
|
||||
|
||||
timespec filetime;
|
||||
if (FileModificationTime(lock_path, &filetime) &&
|
||||
filetime.tv_sec > now + lockfile_ttl) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ScopedFileHandle lock_fd(LoggingOpenFileForReadAndWrite(
|
||||
lock_path, FileWriteMode::kReuseOrFail, FilePermissions::kOwnerOnly));
|
||||
if (!lock_fd.is_valid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
time_t timestamp;
|
||||
if (!LoggingReadFileExactly(lock_fd.get(), ×tamp, sizeof(timestamp))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return now >= timestamp + lockfile_ttl;
|
||||
}
|
||||
|
||||
private:
|
||||
ScopedRemoveFile lock_file_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ScopedLockFile);
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
class CrashReportDatabaseGeneric : public CrashReportDatabase {
|
||||
public:
|
||||
CrashReportDatabaseGeneric();
|
||||
~CrashReportDatabaseGeneric() override;
|
||||
|
||||
bool Initialize(const base::FilePath& path, bool may_create);
|
||||
|
||||
// CrashReportDatabase:
|
||||
Settings* GetSettings() override;
|
||||
OperationStatus PrepareNewCrashReport(
|
||||
std::unique_ptr<NewReport>* report) override;
|
||||
OperationStatus FinishedWritingCrashReport(std::unique_ptr<NewReport> report,
|
||||
UUID* uuid) override;
|
||||
OperationStatus LookUpCrashReport(const UUID& uuid, Report* report) override;
|
||||
OperationStatus GetPendingReports(std::vector<Report>* reports) override;
|
||||
OperationStatus GetCompletedReports(std::vector<Report>* reports) override;
|
||||
OperationStatus GetReportForUploading(
|
||||
const UUID& uuid,
|
||||
std::unique_ptr<const UploadReport>* report) override;
|
||||
OperationStatus SkipReportUpload(const UUID& uuid,
|
||||
Metrics::CrashSkippedReason reason) override;
|
||||
OperationStatus DeleteReport(const UUID& uuid) override;
|
||||
OperationStatus RequestUpload(const UUID& uuid) override;
|
||||
int CleanDatabase(time_t lockfile_ttl) override;
|
||||
|
||||
// Build a filepath for the directory for the report to hold attachments.
|
||||
base::FilePath AttachmentsPath(const UUID& uuid);
|
||||
|
||||
private:
|
||||
struct LockfileUploadReport : public UploadReport {
|
||||
ScopedLockFile lock_file;
|
||||
};
|
||||
|
||||
enum ReportState : int32_t {
|
||||
kUninitialized = -1,
|
||||
|
||||
// Being created by a caller of PrepareNewCrashReport().
|
||||
kNew,
|
||||
|
||||
// Created by FinishedWritingCrashReport(), but not yet uploaded.
|
||||
kPending,
|
||||
|
||||
// Upload completed or skipped.
|
||||
kCompleted,
|
||||
|
||||
// Specifies either kPending or kCompleted.
|
||||
kSearchable,
|
||||
};
|
||||
|
||||
// CrashReportDatabase:
|
||||
OperationStatus RecordUploadAttempt(UploadReport* report,
|
||||
bool successful,
|
||||
const std::string& id) override;
|
||||
|
||||
// Builds a filepath for the report with the specified uuid and state.
|
||||
base::FilePath ReportPath(const UUID& uuid, ReportState state);
|
||||
|
||||
// Locates the report with id uuid and returns its file path in path and a
|
||||
// lock for the report in lock_file. This method succeeds as long as the
|
||||
// report file exists and the lock can be acquired. No validation is done on
|
||||
// the existence or content of the metadata file.
|
||||
OperationStatus LocateAndLockReport(const UUID& uuid,
|
||||
ReportState state,
|
||||
base::FilePath* path,
|
||||
ScopedLockFile* lock_file);
|
||||
|
||||
// Locates, locks, and reads the metadata for the report with the specified
|
||||
// uuid and state. This method will fail and may remove reports if invalid
|
||||
// metadata is detected. state may be kPending, kCompleted, or kSearchable.
|
||||
OperationStatus CheckoutReport(const UUID& uuid,
|
||||
ReportState state,
|
||||
base::FilePath* path,
|
||||
ScopedLockFile* lock_file,
|
||||
Report* report);
|
||||
|
||||
// Reads metadata for all reports in state and returns it in reports.
|
||||
OperationStatus ReportsInState(ReportState state,
|
||||
std::vector<Report>* reports);
|
||||
|
||||
// Cleans lone metadata, reports, or expired locks in a particular state.
|
||||
int CleanReportsInState(ReportState state, time_t lockfile_ttl);
|
||||
|
||||
// Cleans any attachments that have no associated report in any state.
|
||||
void CleanOrphanedAttachments();
|
||||
|
||||
// Attempt to remove any attachments associated with the given report UUID.
|
||||
// There may not be any, so failing is not an error.
|
||||
void RemoveAttachmentsByUUID(const UUID& uuid);
|
||||
|
||||
// Reads the metadata for a report from path and returns it in report.
|
||||
static bool ReadMetadata(const base::FilePath& path, Report* report);
|
||||
|
||||
// Wraps ReadMetadata and removes the report from the database on failure.
|
||||
bool CleaningReadMetadata(const base::FilePath& path, Report* report);
|
||||
|
||||
// Writes metadata for a new report to the filesystem at path.
|
||||
static bool WriteNewMetadata(const base::FilePath& path);
|
||||
|
||||
// Writes the metadata for report to the filesystem at path.
|
||||
static bool WriteMetadata(const base::FilePath& path, const Report& report);
|
||||
|
||||
base::FilePath base_dir_;
|
||||
Settings settings_;
|
||||
InitializationStateDcheck initialized_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(CrashReportDatabaseGeneric);
|
||||
};
|
||||
|
||||
FileWriter* CrashReportDatabase::NewReport::AddAttachment(
|
||||
const std::string& name) {
|
||||
if (!AttachmentNameIsOK(name)) {
|
||||
LOG(ERROR) << "invalid name for attachment " << name;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
base::FilePath attachments_dir =
|
||||
static_cast<CrashReportDatabaseGeneric*>(database_)->AttachmentsPath(
|
||||
uuid_);
|
||||
if (!LoggingCreateDirectory(
|
||||
attachments_dir, FilePermissions::kOwnerOnly, true)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
base::FilePath path = attachments_dir.Append(name);
|
||||
|
||||
auto writer = std::make_unique<FileWriter>();
|
||||
if (!writer->Open(
|
||||
path, FileWriteMode::kCreateOrFail, FilePermissions::kOwnerOnly)) {
|
||||
LOG(ERROR) << "could not open " << path.value();
|
||||
return nullptr;
|
||||
}
|
||||
attachment_writers_.emplace_back(std::move(writer));
|
||||
attachment_removers_.emplace_back(ScopedRemoveFile(path));
|
||||
return attachment_writers_.back().get();
|
||||
}
|
||||
|
||||
void CrashReportDatabase::UploadReport::InitializeAttachments() {
|
||||
base::FilePath attachments_dir =
|
||||
static_cast<CrashReportDatabaseGeneric*>(database_)->AttachmentsPath(
|
||||
uuid);
|
||||
DirectoryReader reader;
|
||||
if (!reader.Open(attachments_dir)) {
|
||||
return;
|
||||
}
|
||||
|
||||
base::FilePath filename;
|
||||
DirectoryReader::Result dir_result;
|
||||
while ((dir_result = reader.NextFile(&filename)) ==
|
||||
DirectoryReader::Result::kSuccess) {
|
||||
const base::FilePath filepath(attachments_dir.Append(filename));
|
||||
std::unique_ptr<FileReader> reader(std::make_unique<FileReader>());
|
||||
if (!reader->Open(filepath)) {
|
||||
LOG(ERROR) << "attachment " << filepath.value()
|
||||
<< " couldn't be opened, skipping";
|
||||
continue;
|
||||
}
|
||||
attachment_readers_.emplace_back(std::move(reader));
|
||||
attachment_map_[filename.value()] = attachment_readers_.back().get();
|
||||
}
|
||||
}
|
||||
|
||||
CrashReportDatabaseGeneric::CrashReportDatabaseGeneric() = default;
|
||||
|
||||
CrashReportDatabaseGeneric::~CrashReportDatabaseGeneric() = default;
|
||||
|
||||
bool CrashReportDatabaseGeneric::Initialize(const base::FilePath& path,
|
||||
bool may_create) {
|
||||
INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
|
||||
base_dir_ = path;
|
||||
|
||||
if (!IsDirectory(base_dir_, true) &&
|
||||
!(may_create &&
|
||||
LoggingCreateDirectory(base_dir_, FilePermissions::kOwnerOnly, true))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const base::FilePath::CharType* subdir : kReportDirectories) {
|
||||
if (!LoggingCreateDirectory(
|
||||
base_dir_.Append(subdir), FilePermissions::kOwnerOnly, true)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!LoggingCreateDirectory(base_dir_.Append(kAttachmentsDirectory),
|
||||
FilePermissions::kOwnerOnly,
|
||||
true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!settings_.Initialize(base_dir_.Append(kSettings))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
INITIALIZATION_STATE_SET_VALID(initialized_);
|
||||
return true;
|
||||
}
|
||||
|
||||
// static
|
||||
std::unique_ptr<CrashReportDatabase> CrashReportDatabase::Initialize(
|
||||
const base::FilePath& path) {
|
||||
auto database = std::make_unique<CrashReportDatabaseGeneric>();
|
||||
return database->Initialize(path, true) ? std::move(database) : nullptr;
|
||||
}
|
||||
|
||||
// static
|
||||
std::unique_ptr<CrashReportDatabase>
|
||||
CrashReportDatabase::InitializeWithoutCreating(const base::FilePath& path) {
|
||||
auto database = std::make_unique<CrashReportDatabaseGeneric>();
|
||||
return database->Initialize(path, false) ? std::move(database) : nullptr;
|
||||
}
|
||||
|
||||
Settings* CrashReportDatabaseGeneric::GetSettings() {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
return &settings_;
|
||||
}
|
||||
|
||||
OperationStatus CrashReportDatabaseGeneric::PrepareNewCrashReport(
|
||||
std::unique_ptr<NewReport>* report) {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
|
||||
auto new_report = std::make_unique<NewReport>();
|
||||
if (!new_report->Initialize(
|
||||
this, base_dir_.Append(kNewDirectory), kCrashReportExtension)) {
|
||||
return kFileSystemError;
|
||||
}
|
||||
|
||||
report->reset(new_report.release());
|
||||
return kNoError;
|
||||
}
|
||||
|
||||
OperationStatus CrashReportDatabaseGeneric::FinishedWritingCrashReport(
|
||||
std::unique_ptr<NewReport> report,
|
||||
UUID* uuid) {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
|
||||
base::FilePath path = ReportPath(report->ReportID(), kPending);
|
||||
ScopedLockFile lock_file;
|
||||
if (!lock_file.ResetAcquire(path)) {
|
||||
return kBusyError;
|
||||
}
|
||||
|
||||
if (!WriteNewMetadata(ReplaceFinalExtension(path, kMetadataExtension))) {
|
||||
return kDatabaseError;
|
||||
}
|
||||
|
||||
FileOffset size = report->Writer()->Seek(0, SEEK_END);
|
||||
|
||||
report->Writer()->Close();
|
||||
if (!MoveFileOrDirectory(report->file_remover_.get(), path)) {
|
||||
return kFileSystemError;
|
||||
}
|
||||
// We've moved the report to pending, so it no longer needs to be removed.
|
||||
ignore_result(report->file_remover_.release());
|
||||
|
||||
// Close all the attachments and disarm their removers too.
|
||||
for (auto& writer : report->attachment_writers_) {
|
||||
writer->Close();
|
||||
}
|
||||
for (auto& remover : report->attachment_removers_) {
|
||||
ignore_result(remover.release());
|
||||
}
|
||||
|
||||
*uuid = report->ReportID();
|
||||
|
||||
Metrics::CrashReportPending(Metrics::PendingReportReason::kNewlyCreated);
|
||||
Metrics::CrashReportSize(size);
|
||||
|
||||
return kNoError;
|
||||
}
|
||||
|
||||
OperationStatus CrashReportDatabaseGeneric::LookUpCrashReport(const UUID& uuid,
|
||||
Report* report) {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
|
||||
ScopedLockFile lock_file;
|
||||
base::FilePath path;
|
||||
return CheckoutReport(uuid, kSearchable, &path, &lock_file, report);
|
||||
}
|
||||
|
||||
OperationStatus CrashReportDatabaseGeneric::GetPendingReports(
|
||||
std::vector<Report>* reports) {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
return ReportsInState(kPending, reports);
|
||||
}
|
||||
|
||||
OperationStatus CrashReportDatabaseGeneric::GetCompletedReports(
|
||||
std::vector<Report>* reports) {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
return ReportsInState(kCompleted, reports);
|
||||
}
|
||||
|
||||
OperationStatus CrashReportDatabaseGeneric::GetReportForUploading(
|
||||
const UUID& uuid,
|
||||
std::unique_ptr<const UploadReport>* report) {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
|
||||
auto upload_report = std::make_unique<LockfileUploadReport>();
|
||||
|
||||
base::FilePath path;
|
||||
OperationStatus os = CheckoutReport(
|
||||
uuid, kPending, &path, &upload_report->lock_file, upload_report.get());
|
||||
if (os != kNoError) {
|
||||
return os;
|
||||
}
|
||||
|
||||
if (!upload_report->Initialize(path, this)) {
|
||||
return kFileSystemError;
|
||||
}
|
||||
|
||||
report->reset(upload_report.release());
|
||||
return kNoError;
|
||||
}
|
||||
|
||||
OperationStatus CrashReportDatabaseGeneric::SkipReportUpload(
|
||||
const UUID& uuid,
|
||||
Metrics::CrashSkippedReason reason) {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
|
||||
Metrics::CrashUploadSkipped(reason);
|
||||
|
||||
base::FilePath path;
|
||||
ScopedLockFile lock_file;
|
||||
Report report;
|
||||
OperationStatus os =
|
||||
CheckoutReport(uuid, kPending, &path, &lock_file, &report);
|
||||
if (os != kNoError) {
|
||||
return os;
|
||||
}
|
||||
|
||||
base::FilePath completed_path(ReportPath(uuid, kCompleted));
|
||||
ScopedLockFile completed_lock_file;
|
||||
if (!completed_lock_file.ResetAcquire(completed_path)) {
|
||||
return kBusyError;
|
||||
}
|
||||
|
||||
report.upload_explicitly_requested = false;
|
||||
if (!WriteMetadata(completed_path, report)) {
|
||||
return kDatabaseError;
|
||||
}
|
||||
|
||||
if (!MoveFileOrDirectory(path, completed_path)) {
|
||||
return kFileSystemError;
|
||||
}
|
||||
|
||||
if (!LoggingRemoveFile(ReplaceFinalExtension(path, kMetadataExtension))) {
|
||||
return kDatabaseError;
|
||||
}
|
||||
|
||||
return kNoError;
|
||||
}
|
||||
|
||||
OperationStatus CrashReportDatabaseGeneric::DeleteReport(const UUID& uuid) {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
|
||||
base::FilePath path;
|
||||
ScopedLockFile lock_file;
|
||||
OperationStatus os =
|
||||
LocateAndLockReport(uuid, kSearchable, &path, &lock_file);
|
||||
if (os != kNoError) {
|
||||
return os;
|
||||
}
|
||||
|
||||
if (!LoggingRemoveFile(path)) {
|
||||
return kFileSystemError;
|
||||
}
|
||||
|
||||
if (!LoggingRemoveFile(ReplaceFinalExtension(path, kMetadataExtension))) {
|
||||
return kDatabaseError;
|
||||
}
|
||||
|
||||
RemoveAttachmentsByUUID(uuid);
|
||||
|
||||
return kNoError;
|
||||
}
|
||||
|
||||
OperationStatus CrashReportDatabaseGeneric::RequestUpload(const UUID& uuid) {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
|
||||
base::FilePath path;
|
||||
ScopedLockFile lock_file;
|
||||
Report report;
|
||||
OperationStatus os =
|
||||
CheckoutReport(uuid, kSearchable, &path, &lock_file, &report);
|
||||
if (os != kNoError) {
|
||||
return os;
|
||||
}
|
||||
|
||||
if (report.uploaded) {
|
||||
return kCannotRequestUpload;
|
||||
}
|
||||
|
||||
report.upload_explicitly_requested = true;
|
||||
base::FilePath pending_path = ReportPath(uuid, kPending);
|
||||
if (!MoveFileOrDirectory(path, pending_path)) {
|
||||
return kFileSystemError;
|
||||
}
|
||||
|
||||
if (!WriteMetadata(pending_path, report)) {
|
||||
return kDatabaseError;
|
||||
}
|
||||
|
||||
if (pending_path != path) {
|
||||
if (!LoggingRemoveFile(ReplaceFinalExtension(path, kMetadataExtension))) {
|
||||
return kDatabaseError;
|
||||
}
|
||||
}
|
||||
|
||||
Metrics::CrashReportPending(Metrics::PendingReportReason::kUserInitiated);
|
||||
return kNoError;
|
||||
}
|
||||
|
||||
int CrashReportDatabaseGeneric::CleanDatabase(time_t lockfile_ttl) {
|
||||
int removed = 0;
|
||||
time_t now = time(nullptr);
|
||||
|
||||
DirectoryReader reader;
|
||||
const base::FilePath new_dir(base_dir_.Append(kNewDirectory));
|
||||
if (reader.Open(new_dir)) {
|
||||
base::FilePath filename;
|
||||
DirectoryReader::Result result;
|
||||
while ((result = reader.NextFile(&filename)) ==
|
||||
DirectoryReader::Result::kSuccess) {
|
||||
const base::FilePath filepath(new_dir.Append(filename));
|
||||
timespec filetime;
|
||||
if (!FileModificationTime(filepath, &filetime)) {
|
||||
continue;
|
||||
}
|
||||
if (filetime.tv_sec <= now - lockfile_ttl) {
|
||||
if (LoggingRemoveFile(filepath)) {
|
||||
++removed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
removed += CleanReportsInState(kPending, lockfile_ttl);
|
||||
removed += CleanReportsInState(kCompleted, lockfile_ttl);
|
||||
CleanOrphanedAttachments();
|
||||
return removed;
|
||||
}
|
||||
|
||||
OperationStatus CrashReportDatabaseGeneric::RecordUploadAttempt(
|
||||
UploadReport* report,
|
||||
bool successful,
|
||||
const std::string& id) {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
|
||||
Metrics::CrashUploadAttempted(successful);
|
||||
time_t now = time(nullptr);
|
||||
|
||||
report->id = id;
|
||||
report->uploaded = successful;
|
||||
report->last_upload_attempt_time = now;
|
||||
++report->upload_attempts;
|
||||
|
||||
base::FilePath report_path(report->file_path);
|
||||
|
||||
ScopedLockFile lock_file;
|
||||
if (successful) {
|
||||
report->upload_explicitly_requested = false;
|
||||
|
||||
base::FilePath completed_report_path = ReportPath(report->uuid, kCompleted);
|
||||
|
||||
if (!lock_file.ResetAcquire(completed_report_path)) {
|
||||
return kBusyError;
|
||||
}
|
||||
|
||||
report->Reader()->Close();
|
||||
if (!MoveFileOrDirectory(report_path, completed_report_path)) {
|
||||
return kFileSystemError;
|
||||
}
|
||||
|
||||
LoggingRemoveFile(ReplaceFinalExtension(report_path, kMetadataExtension));
|
||||
report_path = completed_report_path;
|
||||
}
|
||||
|
||||
if (!WriteMetadata(report_path, *report)) {
|
||||
return kDatabaseError;
|
||||
}
|
||||
|
||||
if (!settings_.SetLastUploadAttemptTime(now)) {
|
||||
return kDatabaseError;
|
||||
}
|
||||
|
||||
return kNoError;
|
||||
}
|
||||
|
||||
base::FilePath CrashReportDatabaseGeneric::ReportPath(const UUID& uuid,
|
||||
ReportState state) {
|
||||
DCHECK_NE(state, kUninitialized);
|
||||
DCHECK_NE(state, kSearchable);
|
||||
|
||||
#if defined(OS_WIN)
|
||||
const std::wstring uuid_string = uuid.ToString16();
|
||||
#else
|
||||
const std::string uuid_string = uuid.ToString();
|
||||
#endif
|
||||
|
||||
return base_dir_.Append(kReportDirectories[state])
|
||||
.Append(uuid_string + kCrashReportExtension);
|
||||
}
|
||||
|
||||
base::FilePath CrashReportDatabaseGeneric::AttachmentsPath(const UUID& uuid) {
|
||||
#if defined(OS_WIN)
|
||||
const std::wstring uuid_string = uuid.ToString16();
|
||||
#else
|
||||
const std::string uuid_string = uuid.ToString();
|
||||
#endif
|
||||
|
||||
return base_dir_.Append(kAttachmentsDirectory).Append(uuid_string);
|
||||
}
|
||||
|
||||
OperationStatus CrashReportDatabaseGeneric::LocateAndLockReport(
|
||||
const UUID& uuid,
|
||||
ReportState desired_state,
|
||||
base::FilePath* path,
|
||||
ScopedLockFile* lock_file) {
|
||||
std::vector<ReportState> searchable_states;
|
||||
if (desired_state == kSearchable) {
|
||||
searchable_states.push_back(kPending);
|
||||
searchable_states.push_back(kCompleted);
|
||||
} else {
|
||||
DCHECK(desired_state == kPending || desired_state == kCompleted);
|
||||
searchable_states.push_back(desired_state);
|
||||
}
|
||||
|
||||
for (const ReportState state : searchable_states) {
|
||||
base::FilePath local_path(ReportPath(uuid, state));
|
||||
ScopedLockFile local_lock;
|
||||
if (!local_lock.ResetAcquire(local_path)) {
|
||||
return kBusyError;
|
||||
}
|
||||
|
||||
if (!IsRegularFile(local_path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
*path = local_path;
|
||||
*lock_file = std::move(local_lock);
|
||||
return kNoError;
|
||||
}
|
||||
|
||||
return kReportNotFound;
|
||||
}
|
||||
|
||||
OperationStatus CrashReportDatabaseGeneric::CheckoutReport(
|
||||
const UUID& uuid,
|
||||
ReportState state,
|
||||
base::FilePath* path,
|
||||
ScopedLockFile* lock_file,
|
||||
Report* report) {
|
||||
ScopedLockFile local_lock;
|
||||
base::FilePath local_path;
|
||||
OperationStatus os =
|
||||
LocateAndLockReport(uuid, state, &local_path, &local_lock);
|
||||
if (os != kNoError) {
|
||||
return os;
|
||||
}
|
||||
|
||||
if (!CleaningReadMetadata(local_path, report)) {
|
||||
return kDatabaseError;
|
||||
}
|
||||
|
||||
*path = local_path;
|
||||
*lock_file = std::move(local_lock);
|
||||
return kNoError;
|
||||
}
|
||||
|
||||
OperationStatus CrashReportDatabaseGeneric::ReportsInState(
|
||||
ReportState state,
|
||||
std::vector<Report>* reports) {
|
||||
DCHECK(reports->empty());
|
||||
DCHECK_NE(state, kUninitialized);
|
||||
DCHECK_NE(state, kSearchable);
|
||||
DCHECK_NE(state, kNew);
|
||||
|
||||
const base::FilePath dir_path(base_dir_.Append(kReportDirectories[state]));
|
||||
DirectoryReader reader;
|
||||
if (!reader.Open(dir_path)) {
|
||||
return kDatabaseError;
|
||||
}
|
||||
|
||||
base::FilePath filename;
|
||||
DirectoryReader::Result result;
|
||||
while ((result = reader.NextFile(&filename)) ==
|
||||
DirectoryReader::Result::kSuccess) {
|
||||
const base::FilePath::StringType extension(filename.FinalExtension());
|
||||
if (extension.compare(kCrashReportExtension) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const base::FilePath filepath(dir_path.Append(filename));
|
||||
ScopedLockFile lock_file;
|
||||
if (!lock_file.ResetAcquire(filepath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Report report;
|
||||
if (!CleaningReadMetadata(filepath, &report)) {
|
||||
continue;
|
||||
}
|
||||
reports->push_back(report);
|
||||
reports->back().file_path = filepath;
|
||||
}
|
||||
return kNoError;
|
||||
}
|
||||
|
||||
int CrashReportDatabaseGeneric::CleanReportsInState(ReportState state,
|
||||
time_t lockfile_ttl) {
|
||||
const base::FilePath dir_path(base_dir_.Append(kReportDirectories[state]));
|
||||
DirectoryReader reader;
|
||||
if (!reader.Open(dir_path)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int removed = 0;
|
||||
base::FilePath filename;
|
||||
DirectoryReader::Result result;
|
||||
while ((result = reader.NextFile(&filename)) ==
|
||||
DirectoryReader::Result::kSuccess) {
|
||||
const base::FilePath::StringType extension(filename.FinalExtension());
|
||||
const base::FilePath filepath(dir_path.Append(filename));
|
||||
|
||||
// Remove any report files without metadata.
|
||||
if (extension.compare(kCrashReportExtension) == 0) {
|
||||
const base::FilePath metadata_path(
|
||||
ReplaceFinalExtension(filepath, kMetadataExtension));
|
||||
ScopedLockFile report_lock;
|
||||
if (report_lock.ResetAcquire(filepath) && !IsRegularFile(metadata_path) &&
|
||||
LoggingRemoveFile(filepath)) {
|
||||
++removed;
|
||||
RemoveAttachmentsByUUID(UUIDFromReportPath(filepath));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove any metadata files without report files.
|
||||
if (extension.compare(kMetadataExtension) == 0) {
|
||||
const base::FilePath report_path(
|
||||
ReplaceFinalExtension(filepath, kCrashReportExtension));
|
||||
ScopedLockFile report_lock;
|
||||
if (report_lock.ResetAcquire(report_path) &&
|
||||
!IsRegularFile(report_path) && LoggingRemoveFile(filepath)) {
|
||||
++removed;
|
||||
RemoveAttachmentsByUUID(UUIDFromReportPath(filepath));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove any expired locks only if we can remove the report and metadata.
|
||||
if (extension.compare(kLockExtension) == 0 &&
|
||||
ScopedLockFile::IsExpired(filepath, lockfile_ttl)) {
|
||||
const base::FilePath no_ext(filepath.RemoveFinalExtension());
|
||||
const base::FilePath report_path(no_ext.value() + kCrashReportExtension);
|
||||
const base::FilePath metadata_path(no_ext.value() + kMetadataExtension);
|
||||
if ((IsRegularFile(report_path) && !LoggingRemoveFile(report_path)) ||
|
||||
(IsRegularFile(metadata_path) && !LoggingRemoveFile(metadata_path))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (LoggingRemoveFile(filepath)) {
|
||||
++removed;
|
||||
RemoveAttachmentsByUUID(UUIDFromReportPath(filepath));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
void CrashReportDatabaseGeneric::CleanOrphanedAttachments() {
|
||||
base::FilePath root_attachments_dir(base_dir_.Append(kAttachmentsDirectory));
|
||||
DirectoryReader reader;
|
||||
if (!reader.Open(root_attachments_dir)) {
|
||||
LOG(ERROR) << "no attachments dir";
|
||||
return;
|
||||
}
|
||||
|
||||
base::FilePath filename;
|
||||
DirectoryReader::Result result;
|
||||
while ((result = reader.NextFile(&filename)) ==
|
||||
DirectoryReader::Result::kSuccess) {
|
||||
const base::FilePath path(root_attachments_dir.Append(filename));
|
||||
if (IsDirectory(path, false)) {
|
||||
UUID uuid;
|
||||
if (!uuid.InitializeFromString(filename.value())) {
|
||||
LOG(ERROR) << "unexpected attachment dir name " << filename.value();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check to see if the report is being created in "new".
|
||||
base::FilePath new_dir_path =
|
||||
base_dir_.Append(kNewDirectory)
|
||||
.Append(uuid.ToString() + kCrashReportExtension);
|
||||
if (IsRegularFile(new_dir_path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check to see if the report is in "pending" or "completed".
|
||||
ScopedLockFile local_lock;
|
||||
base::FilePath local_path;
|
||||
OperationStatus os =
|
||||
LocateAndLockReport(uuid, kSearchable, &local_path, &local_lock);
|
||||
if (os != kReportNotFound) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Couldn't find a report, assume these attachments are orphaned.
|
||||
RemoveAttachmentsByUUID(uuid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CrashReportDatabaseGeneric::RemoveAttachmentsByUUID(const UUID& uuid) {
|
||||
base::FilePath attachments_dir = AttachmentsPath(uuid);
|
||||
DirectoryReader reader;
|
||||
if (!reader.Open(attachments_dir)) {
|
||||
return;
|
||||
}
|
||||
|
||||
base::FilePath filename;
|
||||
DirectoryReader::Result result;
|
||||
while ((result = reader.NextFile(&filename)) ==
|
||||
DirectoryReader::Result::kSuccess) {
|
||||
const base::FilePath filepath(attachments_dir.Append(filename));
|
||||
LoggingRemoveFile(filepath);
|
||||
}
|
||||
|
||||
LoggingRemoveDirectory(attachments_dir);
|
||||
}
|
||||
|
||||
// static
|
||||
bool CrashReportDatabaseGeneric::ReadMetadata(const base::FilePath& path,
|
||||
Report* report) {
|
||||
const base::FilePath metadata_path(
|
||||
ReplaceFinalExtension(path, kMetadataExtension));
|
||||
|
||||
ScopedFileHandle handle(LoggingOpenFileForRead(metadata_path));
|
||||
if (!handle.is_valid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!report->uuid.InitializeFromString(
|
||||
path.BaseName().RemoveFinalExtension().value())) {
|
||||
LOG(ERROR) << "Couldn't interpret report uuid";
|
||||
return false;
|
||||
}
|
||||
|
||||
ReportMetadata metadata;
|
||||
if (!LoggingReadFileExactly(handle.get(), &metadata, sizeof(metadata))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (metadata.version != ReportMetadata::kVersion) {
|
||||
LOG(ERROR) << "metadata version mismatch";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!LoggingReadToEOF(handle.get(), &report->id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
report->upload_attempts = metadata.upload_attempts;
|
||||
report->last_upload_attempt_time = metadata.last_upload_attempt_time;
|
||||
report->creation_time = metadata.creation_time;
|
||||
report->uploaded = (metadata.attributes & kAttributeUploaded) != 0;
|
||||
report->upload_explicitly_requested =
|
||||
(metadata.attributes & kAttributeUploadExplicitlyRequested) != 0;
|
||||
report->file_path = path;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CrashReportDatabaseGeneric::CleaningReadMetadata(
|
||||
const base::FilePath& path,
|
||||
Report* report) {
|
||||
if (ReadMetadata(path, report)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
LoggingRemoveFile(path);
|
||||
LoggingRemoveFile(ReplaceFinalExtension(path, kMetadataExtension));
|
||||
RemoveAttachmentsByUUID(report->uuid);
|
||||
return false;
|
||||
}
|
||||
|
||||
// static
|
||||
bool CrashReportDatabaseGeneric::WriteNewMetadata(const base::FilePath& path) {
|
||||
const base::FilePath metadata_path(
|
||||
ReplaceFinalExtension(path, kMetadataExtension));
|
||||
|
||||
ScopedFileHandle handle(LoggingOpenFileForWrite(metadata_path,
|
||||
FileWriteMode::kCreateOrFail,
|
||||
FilePermissions::kOwnerOnly));
|
||||
if (!handle.is_valid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ReportMetadata metadata;
|
||||
metadata.creation_time = time(nullptr);
|
||||
|
||||
return LoggingWriteFile(handle.get(), &metadata, sizeof(metadata));
|
||||
}
|
||||
|
||||
// static
|
||||
bool CrashReportDatabaseGeneric::WriteMetadata(const base::FilePath& path,
|
||||
const Report& report) {
|
||||
const base::FilePath metadata_path(
|
||||
ReplaceFinalExtension(path, kMetadataExtension));
|
||||
|
||||
ScopedFileHandle handle(
|
||||
LoggingOpenFileForWrite(metadata_path,
|
||||
FileWriteMode::kTruncateOrCreate,
|
||||
FilePermissions::kOwnerOnly));
|
||||
if (!handle.is_valid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ReportMetadata metadata;
|
||||
metadata.creation_time = report.creation_time;
|
||||
metadata.last_upload_attempt_time = report.last_upload_attempt_time;
|
||||
metadata.upload_attempts = report.upload_attempts;
|
||||
metadata.attributes =
|
||||
(report.uploaded ? kAttributeUploaded : 0) |
|
||||
(report.upload_explicitly_requested ? kAttributeUploadExplicitlyRequested
|
||||
: 0);
|
||||
|
||||
return LoggingWriteFile(handle.get(), &metadata, sizeof(metadata)) &&
|
||||
LoggingWriteFile(handle.get(), report.id.c_str(), report.id.size());
|
||||
}
|
||||
|
||||
} // namespace crashpad
|
@ -107,6 +107,8 @@ std::string XattrNameInternal(const base::StringPiece& name, bool new_name) {
|
||||
name.data());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
//! \brief A CrashReportDatabase that uses HFS+ extended attributes to store
|
||||
//! report metadata.
|
||||
//!
|
||||
@ -130,24 +132,27 @@ class CrashReportDatabaseMac : public CrashReportDatabase {
|
||||
|
||||
// CrashReportDatabase:
|
||||
Settings* GetSettings() override;
|
||||
OperationStatus PrepareNewCrashReport(NewReport** report) override;
|
||||
OperationStatus FinishedWritingCrashReport(NewReport* report,
|
||||
OperationStatus PrepareNewCrashReport(
|
||||
std::unique_ptr<NewReport>* report) override;
|
||||
OperationStatus FinishedWritingCrashReport(std::unique_ptr<NewReport> report,
|
||||
UUID* uuid) override;
|
||||
OperationStatus ErrorWritingCrashReport(NewReport* report) override;
|
||||
OperationStatus LookUpCrashReport(const UUID& uuid, Report* report) override;
|
||||
OperationStatus GetPendingReports(std::vector<Report>* reports) override;
|
||||
OperationStatus GetCompletedReports(std::vector<Report>* reports) override;
|
||||
OperationStatus GetReportForUploading(const UUID& uuid,
|
||||
const Report** report) override;
|
||||
OperationStatus RecordUploadAttempt(const Report* report,
|
||||
bool successful,
|
||||
const std::string& id) override;
|
||||
OperationStatus GetReportForUploading(
|
||||
const UUID& uuid,
|
||||
std::unique_ptr<const UploadReport>* report) override;
|
||||
OperationStatus SkipReportUpload(const UUID& uuid,
|
||||
Metrics::CrashSkippedReason reason) override;
|
||||
OperationStatus DeleteReport(const UUID& uuid) override;
|
||||
OperationStatus RequestUpload(const UUID& uuid) override;
|
||||
|
||||
private:
|
||||
// CrashReportDatabase:
|
||||
OperationStatus RecordUploadAttempt(UploadReport* report,
|
||||
bool successful,
|
||||
const std::string& id) override;
|
||||
|
||||
//! \brief Report states for use with LocateCrashReport().
|
||||
//!
|
||||
//! ReportState may be considered to be a bitfield.
|
||||
@ -161,10 +166,10 @@ class CrashReportDatabaseMac : public CrashReportDatabase {
|
||||
|
||||
//! \brief A private extension of the Report class that maintains bookkeeping
|
||||
//! information of the database.
|
||||
struct UploadReport : public Report {
|
||||
struct UploadReportMac : public UploadReport {
|
||||
//! \brief Stores the flock of the file for the duration of
|
||||
//! GetReportForUploading() and RecordUploadAttempt().
|
||||
int lock_fd;
|
||||
base::ScopedFD lock_fd;
|
||||
};
|
||||
|
||||
//! \brief Locates a crash report in the database by UUID.
|
||||
@ -240,10 +245,20 @@ class CrashReportDatabaseMac : public CrashReportDatabase {
|
||||
DISALLOW_COPY_AND_ASSIGN(CrashReportDatabaseMac);
|
||||
};
|
||||
|
||||
FileWriter* CrashReportDatabase::NewReport::AddAttachment(
|
||||
const std::string& name) {
|
||||
// Attachments aren't implemented in the Mac database yet.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void CrashReportDatabase::UploadReport::InitializeAttachments() {
|
||||
// Attachments aren't implemented in the Mac database yet.
|
||||
}
|
||||
|
||||
CrashReportDatabaseMac::CrashReportDatabaseMac(const base::FilePath& path)
|
||||
: CrashReportDatabase(),
|
||||
base_dir_(path),
|
||||
settings_(base_dir_.Append(kSettings)),
|
||||
settings_(),
|
||||
xattr_new_names_(false),
|
||||
initialized_() {
|
||||
}
|
||||
@ -268,7 +283,7 @@ bool CrashReportDatabaseMac::Initialize(bool may_create) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!settings_.Initialize())
|
||||
if (!settings_.Initialize(base_dir_.Append(kSettings)))
|
||||
return false;
|
||||
|
||||
// Do an xattr operation as the last step, to ensure the filesystem has
|
||||
@ -301,103 +316,68 @@ Settings* CrashReportDatabaseMac::GetSettings() {
|
||||
}
|
||||
|
||||
CrashReportDatabase::OperationStatus
|
||||
CrashReportDatabaseMac::PrepareNewCrashReport(NewReport** out_report) {
|
||||
CrashReportDatabaseMac::PrepareNewCrashReport(
|
||||
std::unique_ptr<NewReport>* out_report) {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
|
||||
std::unique_ptr<NewReport> report(new NewReport());
|
||||
|
||||
uuid_t uuid_gen;
|
||||
uuid_generate(uuid_gen);
|
||||
report->uuid.InitializeFromBytes(uuid_gen);
|
||||
|
||||
report->path =
|
||||
base_dir_.Append(kWriteDirectory)
|
||||
.Append(report->uuid.ToString() + "." + kCrashReportFileExtension);
|
||||
|
||||
report->handle = HANDLE_EINTR(
|
||||
open(report->path.value().c_str(),
|
||||
O_WRONLY | O_EXLOCK | O_CREAT | O_EXCL | O_NOCTTY | O_CLOEXEC,
|
||||
0600));
|
||||
if (report->handle < 0) {
|
||||
PLOG(ERROR) << "open " << report->path.value();
|
||||
if (!report->Initialize(this,
|
||||
base_dir_.Append(kWriteDirectory),
|
||||
std::string(".") + kCrashReportFileExtension)) {
|
||||
return kFileSystemError;
|
||||
}
|
||||
|
||||
// TODO(rsesek): Potentially use an fsetxattr() here instead.
|
||||
if (!WriteXattr(
|
||||
report->path, XattrName(kXattrUUID), report->uuid.ToString())) {
|
||||
PLOG_IF(ERROR, IGNORE_EINTR(close(report->handle)) != 0) << "close";
|
||||
if (!WriteXattr(report->file_remover_.get(),
|
||||
XattrName(kXattrUUID),
|
||||
report->ReportID().ToString())) {
|
||||
return kDatabaseError;
|
||||
}
|
||||
|
||||
*out_report = report.release();
|
||||
|
||||
out_report->reset(report.release());
|
||||
return kNoError;
|
||||
}
|
||||
|
||||
CrashReportDatabase::OperationStatus
|
||||
CrashReportDatabaseMac::FinishedWritingCrashReport(NewReport* report,
|
||||
UUID* uuid) {
|
||||
CrashReportDatabaseMac::FinishedWritingCrashReport(
|
||||
std::unique_ptr<NewReport> report,
|
||||
UUID* uuid) {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
|
||||
// Takes ownership of the |handle| and the O_EXLOCK.
|
||||
base::ScopedFD lock(report->handle);
|
||||
|
||||
// Take ownership of the report.
|
||||
std::unique_ptr<NewReport> scoped_report(report);
|
||||
const base::FilePath& path = report->file_remover_.get();
|
||||
|
||||
// Get the report's UUID to return.
|
||||
std::string uuid_string;
|
||||
if (ReadXattr(report->path, XattrName(kXattrUUID),
|
||||
&uuid_string) != XattrStatus::kOK ||
|
||||
if (ReadXattr(path, XattrName(kXattrUUID), &uuid_string) !=
|
||||
XattrStatus::kOK ||
|
||||
!uuid->InitializeFromString(uuid_string)) {
|
||||
LOG(ERROR) << "Failed to read UUID for crash report "
|
||||
<< report->path.value();
|
||||
LOG(ERROR) << "Failed to read UUID for crash report " << path.value();
|
||||
return kDatabaseError;
|
||||
}
|
||||
|
||||
if (*uuid != report->uuid) {
|
||||
LOG(ERROR) << "UUID mismatch for crash report " << report->path.value();
|
||||
if (*uuid != report->ReportID()) {
|
||||
LOG(ERROR) << "UUID mismatch for crash report " << path.value();
|
||||
return kDatabaseError;
|
||||
}
|
||||
|
||||
// Record the creation time of this report.
|
||||
if (!WriteXattrTimeT(report->path, XattrName(kXattrCreationTime),
|
||||
time(nullptr))) {
|
||||
if (!WriteXattrTimeT(path, XattrName(kXattrCreationTime), time(nullptr))) {
|
||||
return kDatabaseError;
|
||||
}
|
||||
|
||||
FileOffset size = report->Writer()->Seek(0, SEEK_END);
|
||||
|
||||
// Move the report to its new location for uploading.
|
||||
base::FilePath new_path =
|
||||
base_dir_.Append(kUploadPendingDirectory).Append(report->path.BaseName());
|
||||
if (rename(report->path.value().c_str(), new_path.value().c_str()) != 0) {
|
||||
PLOG(ERROR) << "rename " << report->path.value() << " to "
|
||||
<< new_path.value();
|
||||
base_dir_.Append(kUploadPendingDirectory).Append(path.BaseName());
|
||||
if (rename(path.value().c_str(), new_path.value().c_str()) != 0) {
|
||||
PLOG(ERROR) << "rename " << path.value() << " to " << new_path.value();
|
||||
return kFileSystemError;
|
||||
}
|
||||
ignore_result(report->file_remover_.release());
|
||||
|
||||
Metrics::CrashReportPending(Metrics::PendingReportReason::kNewlyCreated);
|
||||
Metrics::CrashReportSize(report->handle);
|
||||
|
||||
return kNoError;
|
||||
}
|
||||
|
||||
CrashReportDatabase::OperationStatus
|
||||
CrashReportDatabaseMac::ErrorWritingCrashReport(NewReport* report) {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
|
||||
// Takes ownership of the |handle| and the O_EXLOCK.
|
||||
base::ScopedFD lock(report->handle);
|
||||
|
||||
// Take ownership of the report.
|
||||
std::unique_ptr<NewReport> scoped_report(report);
|
||||
|
||||
// Remove the file that the report would have been written to had no error
|
||||
// occurred.
|
||||
if (unlink(report->path.value().c_str()) != 0) {
|
||||
PLOG(ERROR) << "unlink " << report->path.value();
|
||||
return kFileSystemError;
|
||||
}
|
||||
Metrics::CrashReportSize(size);
|
||||
|
||||
return kNoError;
|
||||
}
|
||||
@ -440,31 +420,36 @@ CrashReportDatabaseMac::GetCompletedReports(
|
||||
}
|
||||
|
||||
CrashReportDatabase::OperationStatus
|
||||
CrashReportDatabaseMac::GetReportForUploading(const UUID& uuid,
|
||||
const Report** report) {
|
||||
CrashReportDatabaseMac::GetReportForUploading(
|
||||
const UUID& uuid,
|
||||
std::unique_ptr<const UploadReport>* report) {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
|
||||
base::FilePath report_path = LocateCrashReport(uuid, kReportStatePending);
|
||||
if (report_path.empty())
|
||||
auto upload_report = std::make_unique<UploadReportMac>();
|
||||
|
||||
upload_report->file_path = LocateCrashReport(uuid, kReportStatePending);
|
||||
if (upload_report->file_path.empty())
|
||||
return kReportNotFound;
|
||||
|
||||
std::unique_ptr<UploadReport> upload_report(new UploadReport());
|
||||
upload_report->file_path = report_path;
|
||||
|
||||
base::ScopedFD lock(ObtainReportLock(report_path));
|
||||
base::ScopedFD lock(ObtainReportLock(upload_report->file_path));
|
||||
if (!lock.is_valid())
|
||||
return kBusyError;
|
||||
|
||||
if (!ReadReportMetadataLocked(report_path, upload_report.get()))
|
||||
if (!ReadReportMetadataLocked(upload_report->file_path, upload_report.get()))
|
||||
return kDatabaseError;
|
||||
|
||||
upload_report->lock_fd = lock.release();
|
||||
*report = upload_report.release();
|
||||
if (!upload_report->reader_->Open(upload_report->file_path)) {
|
||||
return kFileSystemError;
|
||||
}
|
||||
|
||||
upload_report->database_ = this;
|
||||
upload_report->lock_fd.reset(lock.release());
|
||||
report->reset(upload_report.release());
|
||||
return kNoError;
|
||||
}
|
||||
|
||||
CrashReportDatabase::OperationStatus
|
||||
CrashReportDatabaseMac::RecordUploadAttempt(const Report* report,
|
||||
CrashReportDatabaseMac::RecordUploadAttempt(UploadReport* report,
|
||||
bool successful,
|
||||
const std::string& id) {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
@ -479,13 +464,6 @@ CrashReportDatabaseMac::RecordUploadAttempt(const Report* report,
|
||||
if (report_path.empty())
|
||||
return kReportNotFound;
|
||||
|
||||
std::unique_ptr<const UploadReport> upload_report(
|
||||
static_cast<const UploadReport*>(report));
|
||||
|
||||
base::ScopedFD lock(upload_report->lock_fd);
|
||||
if (!lock.is_valid())
|
||||
return kBusyError;
|
||||
|
||||
if (successful) {
|
||||
CrashReportDatabase::OperationStatus os =
|
||||
MarkReportCompletedLocked(report_path, &report_path);
|
||||
@ -774,8 +752,6 @@ std::unique_ptr<CrashReportDatabase> InitializeInternal(
|
||||
return std::unique_ptr<CrashReportDatabase>(database_mac.release());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// static
|
||||
std::unique_ptr<CrashReportDatabase> CrashReportDatabase::Initialize(
|
||||
const base::FilePath& path) {
|
||||
|
@ -19,8 +19,11 @@
|
||||
#include "gtest/gtest.h"
|
||||
#include "test/errors.h"
|
||||
#include "test/file.h"
|
||||
#include "test/filesystem.h"
|
||||
#include "test/gtest_disabled.h"
|
||||
#include "test/scoped_temp_dir.h"
|
||||
#include "util/file/file_io.h"
|
||||
#include "util/file/filesystem.h"
|
||||
|
||||
namespace crashpad {
|
||||
namespace test {
|
||||
@ -48,20 +51,19 @@ class CrashReportDatabaseTest : public testing::Test {
|
||||
}
|
||||
|
||||
void CreateCrashReport(CrashReportDatabase::Report* report) {
|
||||
CrashReportDatabase::NewReport* new_report = nullptr;
|
||||
std::unique_ptr<CrashReportDatabase::NewReport> new_report;
|
||||
ASSERT_EQ(db_->PrepareNewCrashReport(&new_report),
|
||||
CrashReportDatabase::kNoError);
|
||||
static constexpr char kTest[] = "test";
|
||||
ASSERT_TRUE(LoggingWriteFile(new_report->handle, kTest, sizeof(kTest)));
|
||||
ASSERT_TRUE(new_report->Writer()->Write(kTest, sizeof(kTest)));
|
||||
|
||||
UUID uuid;
|
||||
EXPECT_EQ(db_->FinishedWritingCrashReport(new_report, &uuid),
|
||||
EXPECT_EQ(db_->FinishedWritingCrashReport(std::move(new_report), &uuid),
|
||||
CrashReportDatabase::kNoError);
|
||||
|
||||
EXPECT_EQ(db_->LookUpCrashReport(uuid, report),
|
||||
CrashReportDatabase::kNoError);
|
||||
ExpectPreparedCrashReport(*report);
|
||||
ASSERT_TRUE(FileExists(report->file_path));
|
||||
}
|
||||
|
||||
void UploadReport(const UUID& uuid, bool successful, const std::string& id) {
|
||||
@ -70,15 +72,19 @@ class CrashReportDatabaseTest : public testing::Test {
|
||||
time_t times[2];
|
||||
ASSERT_TRUE(settings->GetLastUploadAttemptTime(×[0]));
|
||||
|
||||
const CrashReportDatabase::Report* report = nullptr;
|
||||
std::unique_ptr<const CrashReportDatabase::UploadReport> report;
|
||||
ASSERT_EQ(db_->GetReportForUploading(uuid, &report),
|
||||
CrashReportDatabase::kNoError);
|
||||
EXPECT_NE(report->uuid, UUID());
|
||||
EXPECT_FALSE(report->file_path.empty());
|
||||
EXPECT_TRUE(FileExists(report->file_path)) << report->file_path.value();
|
||||
EXPECT_GT(report->creation_time, 0);
|
||||
EXPECT_EQ(db_->RecordUploadAttempt(report, successful, id),
|
||||
CrashReportDatabase::kNoError);
|
||||
if (successful) {
|
||||
EXPECT_EQ(db_->RecordUploadComplete(std::move(report), id),
|
||||
CrashReportDatabase::kNoError);
|
||||
} else {
|
||||
report.reset();
|
||||
}
|
||||
|
||||
ASSERT_TRUE(settings->GetLastUploadAttemptTime(×[1]));
|
||||
EXPECT_NE(times[1], 0);
|
||||
@ -176,13 +182,12 @@ TEST_F(CrashReportDatabaseTest, Initialize) {
|
||||
}
|
||||
|
||||
TEST_F(CrashReportDatabaseTest, NewCrashReport) {
|
||||
CrashReportDatabase::NewReport* new_report;
|
||||
std::unique_ptr<CrashReportDatabase::NewReport> new_report;
|
||||
EXPECT_EQ(db()->PrepareNewCrashReport(&new_report),
|
||||
CrashReportDatabase::kNoError);
|
||||
UUID expect_uuid = new_report->uuid;
|
||||
EXPECT_TRUE(FileExists(new_report->path)) << new_report->path.value();
|
||||
UUID expect_uuid = new_report->ReportID();
|
||||
UUID uuid;
|
||||
EXPECT_EQ(db()->FinishedWritingCrashReport(new_report, &uuid),
|
||||
EXPECT_EQ(db()->FinishedWritingCrashReport(std::move(new_report), &uuid),
|
||||
CrashReportDatabase::kNoError);
|
||||
EXPECT_EQ(uuid, expect_uuid);
|
||||
|
||||
@ -201,17 +206,6 @@ TEST_F(CrashReportDatabaseTest, NewCrashReport) {
|
||||
EXPECT_TRUE(reports.empty());
|
||||
}
|
||||
|
||||
TEST_F(CrashReportDatabaseTest, ErrorWritingCrashReport) {
|
||||
CrashReportDatabase::NewReport* new_report = nullptr;
|
||||
ASSERT_EQ(db()->PrepareNewCrashReport(&new_report),
|
||||
CrashReportDatabase::kNoError);
|
||||
base::FilePath new_report_path = new_report->path;
|
||||
EXPECT_TRUE(FileExists(new_report_path)) << new_report_path.value();
|
||||
EXPECT_EQ(db()->ErrorWritingCrashReport(new_report),
|
||||
CrashReportDatabase::kNoError);
|
||||
EXPECT_FALSE(FileExists(new_report_path)) << new_report_path.value();
|
||||
}
|
||||
|
||||
TEST_F(CrashReportDatabaseTest, LookUpCrashReport) {
|
||||
UUID uuid;
|
||||
|
||||
@ -465,16 +459,16 @@ TEST_F(CrashReportDatabaseTest, DuelingUploads) {
|
||||
CrashReportDatabase::Report report;
|
||||
CreateCrashReport(&report);
|
||||
|
||||
const CrashReportDatabase::Report* upload_report;
|
||||
std::unique_ptr<const CrashReportDatabase::UploadReport> upload_report;
|
||||
EXPECT_EQ(db()->GetReportForUploading(report.uuid, &upload_report),
|
||||
CrashReportDatabase::kNoError);
|
||||
|
||||
const CrashReportDatabase::Report* upload_report_2 = nullptr;
|
||||
std::unique_ptr<const CrashReportDatabase::UploadReport> upload_report_2;
|
||||
EXPECT_EQ(db()->GetReportForUploading(report.uuid, &upload_report_2),
|
||||
CrashReportDatabase::kBusyError);
|
||||
EXPECT_FALSE(upload_report_2);
|
||||
|
||||
EXPECT_EQ(db()->RecordUploadAttempt(upload_report, true, std::string()),
|
||||
EXPECT_EQ(db()->RecordUploadComplete(std::move(upload_report), std::string()),
|
||||
CrashReportDatabase::kNoError);
|
||||
}
|
||||
|
||||
@ -482,25 +476,24 @@ TEST_F(CrashReportDatabaseTest, UploadAlreadyUploaded) {
|
||||
CrashReportDatabase::Report report;
|
||||
CreateCrashReport(&report);
|
||||
|
||||
const CrashReportDatabase::Report* upload_report;
|
||||
std::unique_ptr<const CrashReportDatabase::UploadReport> upload_report;
|
||||
EXPECT_EQ(db()->GetReportForUploading(report.uuid, &upload_report),
|
||||
CrashReportDatabase::kNoError);
|
||||
EXPECT_EQ(db()->RecordUploadAttempt(upload_report, true, std::string()),
|
||||
EXPECT_EQ(db()->RecordUploadComplete(std::move(upload_report), std::string()),
|
||||
CrashReportDatabase::kNoError);
|
||||
|
||||
const CrashReportDatabase::Report* upload_report_2 = nullptr;
|
||||
std::unique_ptr<const CrashReportDatabase::UploadReport> upload_report_2;
|
||||
EXPECT_EQ(db()->GetReportForUploading(report.uuid, &upload_report_2),
|
||||
CrashReportDatabase::kReportNotFound);
|
||||
EXPECT_FALSE(upload_report_2);
|
||||
EXPECT_FALSE(upload_report_2.get());
|
||||
}
|
||||
|
||||
TEST_F(CrashReportDatabaseTest, MoveDatabase) {
|
||||
CrashReportDatabase::NewReport* new_report;
|
||||
std::unique_ptr<CrashReportDatabase::NewReport> new_report;
|
||||
EXPECT_EQ(db()->PrepareNewCrashReport(&new_report),
|
||||
CrashReportDatabase::kNoError);
|
||||
EXPECT_TRUE(FileExists(new_report->path)) << new_report->path.value();
|
||||
UUID uuid;
|
||||
EXPECT_EQ(db()->FinishedWritingCrashReport(new_report, &uuid),
|
||||
EXPECT_EQ(db()->FinishedWritingCrashReport(std::move(new_report), &uuid),
|
||||
CrashReportDatabase::kNoError);
|
||||
|
||||
RelocateDatabase();
|
||||
@ -509,28 +502,22 @@ TEST_F(CrashReportDatabaseTest, MoveDatabase) {
|
||||
EXPECT_EQ(db()->LookUpCrashReport(uuid, &report),
|
||||
CrashReportDatabase::kNoError);
|
||||
ExpectPreparedCrashReport(report);
|
||||
EXPECT_TRUE(FileExists(report.file_path)) << report.file_path.value();
|
||||
}
|
||||
|
||||
TEST_F(CrashReportDatabaseTest, ReportRemoved) {
|
||||
CrashReportDatabase::NewReport* new_report;
|
||||
std::unique_ptr<CrashReportDatabase::NewReport> new_report;
|
||||
EXPECT_EQ(db()->PrepareNewCrashReport(&new_report),
|
||||
CrashReportDatabase::kNoError);
|
||||
EXPECT_TRUE(FileExists(new_report->path)) << new_report->path.value();
|
||||
|
||||
UUID uuid;
|
||||
EXPECT_EQ(db()->FinishedWritingCrashReport(new_report, &uuid),
|
||||
EXPECT_EQ(db()->FinishedWritingCrashReport(std::move(new_report), &uuid),
|
||||
CrashReportDatabase::kNoError);
|
||||
|
||||
CrashReportDatabase::Report report;
|
||||
EXPECT_EQ(db()->LookUpCrashReport(uuid, &report),
|
||||
CrashReportDatabase::kNoError);
|
||||
|
||||
#if defined(OS_WIN)
|
||||
EXPECT_EQ(_wunlink(report.file_path.value().c_str()), 0);
|
||||
#else
|
||||
EXPECT_EQ(unlink(report.file_path.value().c_str()), 0)
|
||||
<< ErrnoMessage("unlink");
|
||||
#endif
|
||||
EXPECT_TRUE(LoggingRemoveFile(report.file_path));
|
||||
|
||||
EXPECT_EQ(db()->LookUpCrashReport(uuid, &report),
|
||||
CrashReportDatabase::kReportNotFound);
|
||||
@ -642,21 +629,21 @@ TEST_F(CrashReportDatabaseTest, RequestUpload) {
|
||||
ASSERT_EQ(pending_reports.size(), 2u);
|
||||
|
||||
// Check individual reports.
|
||||
const CrashReportDatabase::Report* expicitly_requested_report;
|
||||
const CrashReportDatabase::Report* explicitly_requested_report;
|
||||
const CrashReportDatabase::Report* pending_report;
|
||||
if (pending_reports[0].uuid == report_0_uuid) {
|
||||
pending_report = &pending_reports[0];
|
||||
expicitly_requested_report = &pending_reports[1];
|
||||
explicitly_requested_report = &pending_reports[1];
|
||||
} else {
|
||||
pending_report = &pending_reports[1];
|
||||
expicitly_requested_report = &pending_reports[0];
|
||||
explicitly_requested_report = &pending_reports[0];
|
||||
}
|
||||
|
||||
EXPECT_EQ(pending_report->uuid, report_0_uuid);
|
||||
EXPECT_FALSE(pending_report->upload_explicitly_requested);
|
||||
|
||||
EXPECT_EQ(expicitly_requested_report->uuid, report_1_uuid);
|
||||
EXPECT_TRUE(expicitly_requested_report->upload_explicitly_requested);
|
||||
EXPECT_EQ(explicitly_requested_report->uuid, report_1_uuid);
|
||||
EXPECT_TRUE(explicitly_requested_report->upload_explicitly_requested);
|
||||
|
||||
// Explicitly requested reports will not have upload_explicitly_requested bit
|
||||
// after getting skipped.
|
||||
@ -683,6 +670,171 @@ TEST_F(CrashReportDatabaseTest, RequestUpload) {
|
||||
CrashReportDatabase::kCannotRequestUpload);
|
||||
}
|
||||
|
||||
TEST_F(CrashReportDatabaseTest, Attachments) {
|
||||
#if defined(OS_MACOSX) || defined(OS_WIN)
|
||||
// Attachments aren't supported on Mac and Windows yet.
|
||||
DISABLED_TEST();
|
||||
#else
|
||||
std::unique_ptr<CrashReportDatabase::NewReport> new_report;
|
||||
ASSERT_EQ(db()->PrepareNewCrashReport(&new_report),
|
||||
CrashReportDatabase::kNoError);
|
||||
|
||||
FileWriter* attach_some_file = new_report->AddAttachment("some_file");
|
||||
ASSERT_NE(attach_some_file, nullptr);
|
||||
static constexpr char test_data[] = "test data";
|
||||
attach_some_file->Write(test_data, sizeof(test_data));
|
||||
|
||||
FileWriter* failed_attach = new_report->AddAttachment("not/a valid fi!e");
|
||||
EXPECT_EQ(failed_attach, nullptr);
|
||||
|
||||
UUID expect_uuid = new_report->ReportID();
|
||||
UUID uuid;
|
||||
ASSERT_EQ(db()->FinishedWritingCrashReport(std::move(new_report), &uuid),
|
||||
CrashReportDatabase::kNoError);
|
||||
EXPECT_EQ(uuid, expect_uuid);
|
||||
|
||||
CrashReportDatabase::Report report;
|
||||
EXPECT_EQ(db()->LookUpCrashReport(uuid, &report),
|
||||
CrashReportDatabase::kNoError);
|
||||
ExpectPreparedCrashReport(report);
|
||||
|
||||
std::vector<CrashReportDatabase::Report> reports;
|
||||
EXPECT_EQ(db()->GetPendingReports(&reports), CrashReportDatabase::kNoError);
|
||||
ASSERT_EQ(reports.size(), 1u);
|
||||
EXPECT_EQ(reports[0].uuid, report.uuid);
|
||||
|
||||
std::unique_ptr<const CrashReportDatabase::UploadReport> upload_report;
|
||||
ASSERT_EQ(db()->GetReportForUploading(reports[0].uuid, &upload_report),
|
||||
CrashReportDatabase::kNoError);
|
||||
std::map<std::string, FileReader*> result_attachments =
|
||||
upload_report->GetAttachments();
|
||||
EXPECT_EQ(result_attachments.size(), 1u);
|
||||
EXPECT_NE(result_attachments.find("some_file"), result_attachments.end());
|
||||
char result_buffer[sizeof(test_data)];
|
||||
result_attachments["some_file"]->Read(result_buffer, sizeof(result_buffer));
|
||||
EXPECT_EQ(memcmp(test_data, result_buffer, sizeof(test_data)), 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST_F(CrashReportDatabaseTest, OrphanedAttachments) {
|
||||
#if defined(OS_MACOSX) || defined(OS_WIN)
|
||||
// Attachments aren't supported on Mac and Windows yet.
|
||||
DISABLED_TEST();
|
||||
#else
|
||||
// TODO: This is using paths that are specific to the generic implementation
|
||||
// and will need to be generalized for Mac and Windows.
|
||||
std::unique_ptr<CrashReportDatabase::NewReport> new_report;
|
||||
ASSERT_EQ(db()->PrepareNewCrashReport(&new_report),
|
||||
CrashReportDatabase::kNoError);
|
||||
|
||||
FileWriter* file1 = new_report->AddAttachment("file1");
|
||||
ASSERT_NE(file1, nullptr);
|
||||
FileWriter* file2 = new_report->AddAttachment("file2");
|
||||
ASSERT_NE(file2, nullptr);
|
||||
|
||||
UUID expect_uuid = new_report->ReportID();
|
||||
UUID uuid;
|
||||
ASSERT_EQ(db()->FinishedWritingCrashReport(std::move(new_report), &uuid),
|
||||
CrashReportDatabase::kNoError);
|
||||
EXPECT_EQ(uuid, expect_uuid);
|
||||
|
||||
CrashReportDatabase::Report report;
|
||||
ASSERT_EQ(db()->LookUpCrashReport(uuid, &report),
|
||||
CrashReportDatabase::kNoError);
|
||||
|
||||
ASSERT_TRUE(LoggingRemoveFile(report.file_path));
|
||||
|
||||
ASSERT_TRUE(LoggingRemoveFile(base::FilePath(
|
||||
report.file_path.RemoveFinalExtension().value() + ".meta")));
|
||||
|
||||
ASSERT_EQ(db()->LookUpCrashReport(uuid, &report),
|
||||
CrashReportDatabase::kReportNotFound);
|
||||
|
||||
base::FilePath report_attachments_dir(
|
||||
path().Append("attachments").Append(uuid.ToString()));
|
||||
base::FilePath file_path1(report_attachments_dir.Append("file1"));
|
||||
base::FilePath file_path2(report_attachments_dir.Append("file2"));
|
||||
EXPECT_TRUE(FileExists(file_path1));
|
||||
EXPECT_TRUE(FileExists(file_path1));
|
||||
|
||||
EXPECT_EQ(db()->CleanDatabase(0), 0);
|
||||
|
||||
EXPECT_FALSE(FileExists(file_path1));
|
||||
EXPECT_FALSE(FileExists(file_path2));
|
||||
EXPECT_FALSE(FileExists(report_attachments_dir));
|
||||
#endif
|
||||
}
|
||||
|
||||
// This test uses knowledge of the database format to break it, so it only
|
||||
// applies to the unfified database implementation.
|
||||
#if !defined(OS_MACOSX) && !defined(OS_WIN)
|
||||
TEST_F(CrashReportDatabaseTest, CleanBrokenDatabase) {
|
||||
// Remove report files if metadata goes missing.
|
||||
CrashReportDatabase::Report report;
|
||||
ASSERT_NO_FATAL_FAILURE(CreateCrashReport(&report));
|
||||
|
||||
const base::FilePath metadata(
|
||||
report.file_path.RemoveFinalExtension().value() +
|
||||
FILE_PATH_LITERAL(".meta"));
|
||||
ASSERT_TRUE(PathExists(report.file_path));
|
||||
ASSERT_TRUE(PathExists(metadata));
|
||||
|
||||
ASSERT_TRUE(LoggingRemoveFile(metadata));
|
||||
EXPECT_EQ(db()->CleanDatabase(0), 1);
|
||||
|
||||
EXPECT_FALSE(PathExists(report.file_path));
|
||||
EXPECT_FALSE(PathExists(metadata));
|
||||
|
||||
// Remove metadata files if reports go missing.
|
||||
ASSERT_NO_FATAL_FAILURE(CreateCrashReport(&report));
|
||||
const base::FilePath metadata2(
|
||||
report.file_path.RemoveFinalExtension().value() +
|
||||
FILE_PATH_LITERAL(".meta"));
|
||||
ASSERT_TRUE(PathExists(report.file_path));
|
||||
ASSERT_TRUE(PathExists(metadata2));
|
||||
|
||||
ASSERT_TRUE(LoggingRemoveFile(report.file_path));
|
||||
EXPECT_EQ(db()->CleanDatabase(0), 1);
|
||||
|
||||
EXPECT_FALSE(PathExists(report.file_path));
|
||||
EXPECT_FALSE(PathExists(metadata2));
|
||||
|
||||
// Remove stale new files.
|
||||
std::unique_ptr<CrashReportDatabase::NewReport> new_report;
|
||||
EXPECT_EQ(db()->PrepareNewCrashReport(&new_report),
|
||||
CrashReportDatabase::kNoError);
|
||||
new_report->Writer()->Close();
|
||||
EXPECT_EQ(db()->CleanDatabase(0), 1);
|
||||
|
||||
// Remove stale lock files and their associated reports.
|
||||
ASSERT_NO_FATAL_FAILURE(CreateCrashReport(&report));
|
||||
const base::FilePath metadata3(
|
||||
report.file_path.RemoveFinalExtension().value() +
|
||||
FILE_PATH_LITERAL(".meta"));
|
||||
ASSERT_TRUE(PathExists(report.file_path));
|
||||
ASSERT_TRUE(PathExists(metadata3));
|
||||
|
||||
const base::FilePath lockpath(
|
||||
report.file_path.RemoveFinalExtension().value() +
|
||||
FILE_PATH_LITERAL(".lock"));
|
||||
ScopedFileHandle handle(LoggingOpenFileForWrite(
|
||||
lockpath, FileWriteMode::kCreateOrFail, FilePermissions::kOwnerOnly));
|
||||
ASSERT_TRUE(handle.is_valid());
|
||||
|
||||
time_t expired_timestamp = time(nullptr) - 60 * 60 * 24 * 3;
|
||||
|
||||
ASSERT_TRUE(LoggingWriteFile(
|
||||
handle.get(), &expired_timestamp, sizeof(expired_timestamp)));
|
||||
ASSERT_TRUE(LoggingCloseFile(handle.get()));
|
||||
ignore_result(handle.release());
|
||||
|
||||
EXPECT_EQ(db()->CleanDatabase(0), 1);
|
||||
|
||||
EXPECT_FALSE(PathExists(report.file_path));
|
||||
EXPECT_FALSE(PathExists(metadata3));
|
||||
}
|
||||
#endif // !OS_MACOSX && !OS_WIN
|
||||
|
||||
} // namespace
|
||||
} // namespace test
|
||||
} // namespace crashpad
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "client/settings.h"
|
||||
#include "util/misc/implicit_cast.h"
|
||||
#include "util/misc/initialization_state_dcheck.h"
|
||||
#include "util/misc/metrics.h"
|
||||
|
||||
@ -571,6 +572,8 @@ bool CreateDirectoryIfNecessary(const base::FilePath& path) {
|
||||
return EnsureDirectory(path);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// CrashReportDatabaseWin ------------------------------------------------------
|
||||
|
||||
class CrashReportDatabaseWin : public CrashReportDatabase {
|
||||
@ -582,24 +585,27 @@ class CrashReportDatabaseWin : public CrashReportDatabase {
|
||||
|
||||
// CrashReportDatabase:
|
||||
Settings* GetSettings() override;
|
||||
OperationStatus PrepareNewCrashReport(NewReport** report) override;
|
||||
OperationStatus FinishedWritingCrashReport(NewReport* report,
|
||||
OperationStatus PrepareNewCrashReport(
|
||||
std::unique_ptr<NewReport>* report) override;
|
||||
OperationStatus FinishedWritingCrashReport(std::unique_ptr<NewReport> report,
|
||||
UUID* uuid) override;
|
||||
OperationStatus ErrorWritingCrashReport(NewReport* report) override;
|
||||
OperationStatus LookUpCrashReport(const UUID& uuid, Report* report) override;
|
||||
OperationStatus GetPendingReports(std::vector<Report>* reports) override;
|
||||
OperationStatus GetCompletedReports(std::vector<Report>* reports) override;
|
||||
OperationStatus GetReportForUploading(const UUID& uuid,
|
||||
const Report** report) override;
|
||||
OperationStatus RecordUploadAttempt(const Report* report,
|
||||
bool successful,
|
||||
const std::string& id) override;
|
||||
OperationStatus GetReportForUploading(
|
||||
const UUID& uuid,
|
||||
std::unique_ptr<const UploadReport>* report) override;
|
||||
OperationStatus SkipReportUpload(const UUID& uuid,
|
||||
Metrics::CrashSkippedReason reason) override;
|
||||
OperationStatus DeleteReport(const UUID& uuid) override;
|
||||
OperationStatus RequestUpload(const UUID& uuid) override;
|
||||
|
||||
private:
|
||||
// CrashReportDatabase:
|
||||
OperationStatus RecordUploadAttempt(UploadReport* report,
|
||||
bool successful,
|
||||
const std::string& id) override;
|
||||
|
||||
std::unique_ptr<Metadata> AcquireMetadata();
|
||||
|
||||
base::FilePath base_dir_;
|
||||
@ -609,13 +615,19 @@ class CrashReportDatabaseWin : public CrashReportDatabase {
|
||||
DISALLOW_COPY_AND_ASSIGN(CrashReportDatabaseWin);
|
||||
};
|
||||
|
||||
CrashReportDatabaseWin::CrashReportDatabaseWin(const base::FilePath& path)
|
||||
: CrashReportDatabase(),
|
||||
base_dir_(path),
|
||||
settings_(base_dir_.Append(kSettings)),
|
||||
initialized_() {
|
||||
FileWriter* CrashReportDatabase::NewReport::AddAttachment(
|
||||
const std::string& name) {
|
||||
// Attachments aren't implemented in the Windows database yet.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void CrashReportDatabase::UploadReport::InitializeAttachments() {
|
||||
// Attachments aren't implemented in the Windows database yet.
|
||||
}
|
||||
|
||||
CrashReportDatabaseWin::CrashReportDatabaseWin(const base::FilePath& path)
|
||||
: CrashReportDatabase(), base_dir_(path), settings_(), initialized_() {}
|
||||
|
||||
CrashReportDatabaseWin::~CrashReportDatabaseWin() {
|
||||
}
|
||||
|
||||
@ -634,7 +646,7 @@ bool CrashReportDatabaseWin::Initialize(bool may_create) {
|
||||
if (!CreateDirectoryIfNecessary(base_dir_.Append(kReportsDirectory)))
|
||||
return false;
|
||||
|
||||
if (!settings_.Initialize())
|
||||
if (!settings_.Initialize(base_dir_.Append(kSettings)))
|
||||
return false;
|
||||
|
||||
INITIALIZATION_STATE_SET_VALID(initialized_);
|
||||
@ -647,67 +659,39 @@ Settings* CrashReportDatabaseWin::GetSettings() {
|
||||
}
|
||||
|
||||
OperationStatus CrashReportDatabaseWin::PrepareNewCrashReport(
|
||||
NewReport** report) {
|
||||
std::unique_ptr<NewReport>* report) {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
|
||||
std::unique_ptr<NewReport> new_report(new NewReport());
|
||||
if (!new_report->uuid.InitializeWithNew())
|
||||
return kFileSystemError;
|
||||
new_report->path = base_dir_.Append(kReportsDirectory)
|
||||
.Append(new_report->uuid.ToString16() + L"." +
|
||||
kCrashReportFileExtension);
|
||||
new_report->handle = LoggingOpenFileForWrite(new_report->path,
|
||||
FileWriteMode::kCreateOrFail,
|
||||
FilePermissions::kOwnerOnly);
|
||||
if (new_report->handle == INVALID_HANDLE_VALUE)
|
||||
if (!new_report->Initialize(this,
|
||||
base_dir_.Append(kReportsDirectory),
|
||||
std::wstring(L".") + kCrashReportFileExtension)) {
|
||||
return kFileSystemError;
|
||||
}
|
||||
|
||||
*report = new_report.release();
|
||||
report->reset(new_report.release());
|
||||
return kNoError;
|
||||
}
|
||||
|
||||
OperationStatus CrashReportDatabaseWin::FinishedWritingCrashReport(
|
||||
NewReport* report,
|
||||
std::unique_ptr<NewReport> report,
|
||||
UUID* uuid) {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
|
||||
// Take ownership of the report.
|
||||
std::unique_ptr<NewReport> scoped_report(report);
|
||||
// Take ownership of the file handle.
|
||||
ScopedFileHandle handle(report->handle);
|
||||
|
||||
std::unique_ptr<Metadata> metadata(AcquireMetadata());
|
||||
if (!metadata)
|
||||
return kDatabaseError;
|
||||
metadata->AddNewRecord(ReportDisk(scoped_report->uuid,
|
||||
scoped_report->path,
|
||||
metadata->AddNewRecord(ReportDisk(report->ReportID(),
|
||||
report->file_remover_.get(),
|
||||
time(nullptr),
|
||||
ReportState::kPending));
|
||||
*uuid = scoped_report->uuid;
|
||||
|
||||
ignore_result(report->file_remover_.release());
|
||||
|
||||
*uuid = report->ReportID();
|
||||
|
||||
Metrics::CrashReportPending(Metrics::PendingReportReason::kNewlyCreated);
|
||||
Metrics::CrashReportSize(handle.get());
|
||||
|
||||
return kNoError;
|
||||
}
|
||||
|
||||
OperationStatus CrashReportDatabaseWin::ErrorWritingCrashReport(
|
||||
NewReport* report) {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
|
||||
// Take ownership of the report.
|
||||
std::unique_ptr<NewReport> scoped_report(report);
|
||||
|
||||
// Close the outstanding handle.
|
||||
LoggingCloseFile(report->handle);
|
||||
|
||||
// We failed to write, so remove the dump file. There's no entry in the
|
||||
// metadata table yet.
|
||||
if (!DeleteFile(scoped_report->path.value().c_str())) {
|
||||
PLOG(ERROR) << "DeleteFile "
|
||||
<< base::UTF16ToUTF8(scoped_report->path.value());
|
||||
return kFileSystemError;
|
||||
}
|
||||
Metrics::CrashReportSize(report->Writer()->Seek(0, SEEK_END));
|
||||
|
||||
return kNoError;
|
||||
}
|
||||
@ -747,44 +731,38 @@ OperationStatus CrashReportDatabaseWin::GetCompletedReports(
|
||||
|
||||
OperationStatus CrashReportDatabaseWin::GetReportForUploading(
|
||||
const UUID& uuid,
|
||||
const Report** report) {
|
||||
std::unique_ptr<const UploadReport>* report) {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
|
||||
std::unique_ptr<Metadata> metadata(AcquireMetadata());
|
||||
if (!metadata)
|
||||
return kDatabaseError;
|
||||
// TODO(scottmg): After returning this report to the client, there is no way
|
||||
// to reap this report if the uploader fails to call RecordUploadAttempt() or
|
||||
// SkipReportUpload() (if it crashed or was otherwise buggy). To resolve this,
|
||||
// one possibility would be to change the interface to be FileHandle based, so
|
||||
// that instead of giving the file_path back to the client and changing state
|
||||
// to kUploading, we return an exclusive access handle, and use that as the
|
||||
// signal that the upload is pending, rather than an update to state in the
|
||||
// metadata. Alternatively, there could be a "garbage collection" at startup
|
||||
// where any reports that are orphaned in the kUploading state are either
|
||||
// reset to kPending to retry, or discarded.
|
||||
|
||||
ReportDisk* report_disk;
|
||||
OperationStatus os = metadata->FindSingleReportAndMarkDirty(
|
||||
uuid, ReportState::kPending, &report_disk);
|
||||
if (os == kNoError) {
|
||||
report_disk->state = ReportState::kUploading;
|
||||
// Create a copy for passing back to client. This will be freed in
|
||||
// RecordUploadAttempt.
|
||||
*report = new Report(*report_disk);
|
||||
auto upload_report = std::make_unique<UploadReport>();
|
||||
*implicit_cast<Report*>(upload_report.get()) = *report_disk;
|
||||
|
||||
if (!upload_report->Initialize(upload_report->file_path, this)) {
|
||||
return kFileSystemError;
|
||||
}
|
||||
|
||||
report->reset(upload_report.release());
|
||||
}
|
||||
return os;
|
||||
}
|
||||
|
||||
OperationStatus CrashReportDatabaseWin::RecordUploadAttempt(
|
||||
const Report* report,
|
||||
UploadReport* report,
|
||||
bool successful,
|
||||
const std::string& id) {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
|
||||
Metrics::CrashUploadAttempted(successful);
|
||||
|
||||
// Take ownership, allocated in GetReportForUploading.
|
||||
std::unique_ptr<const Report> upload_report(report);
|
||||
std::unique_ptr<Metadata> metadata(AcquireMetadata());
|
||||
if (!metadata)
|
||||
return kDatabaseError;
|
||||
@ -903,8 +881,6 @@ OperationStatus CrashReportDatabaseWin::RequestUpload(const UUID& uuid) {
|
||||
return kNoError;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// static
|
||||
std::unique_ptr<CrashReportDatabase> CrashReportDatabase::Initialize(
|
||||
const base::FilePath& path) {
|
||||
|
@ -24,12 +24,16 @@
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/macros.h"
|
||||
#include "build/build_config.h"
|
||||
#include "util/misc/capture_context.h"
|
||||
|
||||
#if defined(OS_MACOSX)
|
||||
#include "base/mac/scoped_mach_port.h"
|
||||
#elif defined(OS_WIN)
|
||||
#include <windows.h>
|
||||
#include "util/win/scoped_handle.h"
|
||||
#elif defined(OS_LINUX) || defined(OS_ANDROID)
|
||||
#include <signal.h>
|
||||
#include <ucontext.h>
|
||||
#endif
|
||||
|
||||
namespace crashpad {
|
||||
@ -71,6 +75,9 @@ class CrashpadClient {
|
||||
//! Crashpad. Optionally, use WaitForHandlerStart() to join with the
|
||||
//! background thread and retrieve the status of handler startup.
|
||||
//!
|
||||
//! On Fuchsia, this method binds to the exception port of the current default
|
||||
//! job, and starts a Crashpad handler to monitor that port.
|
||||
//!
|
||||
//! \param[in] handler The path to a Crashpad handler executable.
|
||||
//! \param[in] database The path to a Crashpad database. The handler will be
|
||||
//! started with this path as its `--database` argument.
|
||||
@ -105,6 +112,105 @@ class CrashpadClient {
|
||||
bool restartable,
|
||||
bool asynchronous_start);
|
||||
|
||||
#if defined(OS_LINUX) || defined(OS_ANDROID) || DOXYGEN
|
||||
//! \brief Installs a signal handler to launch a handler process in reponse to
|
||||
//! a crash.
|
||||
//!
|
||||
//! The handler process will create a crash dump for this process and exit.
|
||||
//!
|
||||
//! \param[in] handler The path to a Crashpad handler executable.
|
||||
//! \param[in] database The path to a Crashpad database. The handler will be
|
||||
//! started with this path as its `--database` argument.
|
||||
//! \param[in] metrics_dir The path to an already existing directory where
|
||||
//! metrics files can be stored. The handler will be started with this
|
||||
//! path as its `--metrics-dir` argument.
|
||||
//! \param[in] url The URL of an upload server. The handler will be started
|
||||
//! with this URL as its `--url` argument.
|
||||
//! \param[in] annotations Process annotations to set in each crash report.
|
||||
//! The handler will be started with an `--annotation` argument for each
|
||||
//! element in this map.
|
||||
//! \param[in] arguments Additional arguments to pass to the Crashpad handler.
|
||||
//! Arguments passed in other parameters and arguments required to perform
|
||||
//! the handshake are the responsibility of this method, and must not be
|
||||
//! specified in this parameter.
|
||||
//!
|
||||
//! \return `true` on success, `false` on failure with a message logged.
|
||||
static bool StartHandlerAtCrash(
|
||||
const base::FilePath& handler,
|
||||
const base::FilePath& database,
|
||||
const base::FilePath& metrics_dir,
|
||||
const std::string& url,
|
||||
const std::map<std::string, std::string>& annotations,
|
||||
const std::vector<std::string>& arguments);
|
||||
|
||||
//! \brief Starts a handler process with an initial client.
|
||||
//!
|
||||
//! This method allows a process to launch the handler process on behalf of
|
||||
//! another process.
|
||||
//!
|
||||
//! \param[in] handler The path to a Crashpad handler executable.
|
||||
//! \param[in] database The path to a Crashpad database. The handler will be
|
||||
//! started with this path as its `--database` argument.
|
||||
//! \param[in] metrics_dir The path to an already existing directory where
|
||||
//! metrics files can be stored. The handler will be started with this
|
||||
//! path as its `--metrics-dir` argument.
|
||||
//! \param[in] url The URL of an upload server. The handler will be started
|
||||
//! with this URL as its `--url` argument.
|
||||
//! \param[in] annotations Process annotations to set in each crash report.
|
||||
//! The handler will be started with an `--annotation` argument for each
|
||||
//! element in this map.
|
||||
//! \param[in] arguments Additional arguments to pass to the Crashpad handler.
|
||||
//! Arguments passed in other parameters and arguments required to perform
|
||||
//! the handshake are the responsibility of this method, and must not be
|
||||
//! specified in this parameter.
|
||||
//! \param[in] socket The server end of a socket pair. The client end should
|
||||
//! be used with an ExceptionHandlerClient.
|
||||
//!
|
||||
//! \return `true` on success, `false` on failure with a message logged.
|
||||
static bool StartHandlerForClient(
|
||||
const base::FilePath& handler,
|
||||
const base::FilePath& database,
|
||||
const base::FilePath& metrics_dir,
|
||||
const std::string& url,
|
||||
const std::map<std::string, std::string>& annotations,
|
||||
const std::vector<std::string>& arguments,
|
||||
int socket);
|
||||
|
||||
//! \brief Requests that the handler capture a dump even though there hasn't
|
||||
//! been a crash.
|
||||
//!
|
||||
//! A handler must have already been installed before calling this method.
|
||||
//!
|
||||
//! TODO(jperaza): Floating point information in the context is zeroed out
|
||||
//! until CaptureContext() supports collecting that information.
|
||||
//!
|
||||
//! \param[in] context A NativeCPUContext, generally captured by
|
||||
//! CaptureContext() or similar.
|
||||
static void DumpWithoutCrash(NativeCPUContext* context);
|
||||
|
||||
//! \brief The type for custom handlers installed by clients.
|
||||
using FirstChanceHandler = bool (*)(int, siginfo_t*, ucontext_t*);
|
||||
|
||||
//! \brief Installs a custom crash signal handler which runs before the
|
||||
//! currently installed Crashpad handler.
|
||||
//!
|
||||
//! Handling signals appropriately can be tricky and use of this method
|
||||
//! should be avoided, if possible.
|
||||
//!
|
||||
//! A handler must have already been installed before calling this method.
|
||||
//!
|
||||
//! The custom handler runs in a signal handler context and must be safe for
|
||||
//! that purpose.
|
||||
//!
|
||||
//! If the custom handler returns `true`, the signal is considered handled and
|
||||
//! the signal handler returns. Otherwise, the currently installed Crashpad
|
||||
//! signal handler is run.
|
||||
//!
|
||||
//! \param[in] handler The custom crash signal handler to install.
|
||||
static void SetFirstChanceExceptionHandler(FirstChanceHandler handler);
|
||||
|
||||
#endif // OS_LINUX || OS_ANDROID || DOXYGEN
|
||||
|
||||
#if defined(OS_MACOSX) || DOXYGEN
|
||||
//! \brief Sets the process’ crash handler to a Mach service registered with
|
||||
//! the bootstrap server.
|
||||
@ -239,9 +345,9 @@ class CrashpadClient {
|
||||
//! used as the exception code in the exception record.
|
||||
//!
|
||||
//! \return `true` if the exception was triggered successfully.
|
||||
bool DumpAndCrashTargetProcess(HANDLE process,
|
||||
HANDLE blame_thread,
|
||||
DWORD exception_code) const;
|
||||
static bool DumpAndCrashTargetProcess(HANDLE process,
|
||||
HANDLE blame_thread,
|
||||
DWORD exception_code);
|
||||
|
||||
enum : uint32_t {
|
||||
//! \brief The exception code (roughly "Client called") used when
|
||||
|
113
client/crashpad_client_fuchsia.cc
Normal file
113
client/crashpad_client_fuchsia.cc
Normal file
@ -0,0 +1,113 @@
|
||||
// Copyright 2017 The Crashpad Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "client/crashpad_client.h"
|
||||
|
||||
#include <lib/fdio/spawn.h>
|
||||
#include <zircon/process.h>
|
||||
#include <zircon/processargs.h>
|
||||
|
||||
#include "base/fuchsia/fuchsia_logging.h"
|
||||
#include "base/fuchsia/scoped_zx_handle.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "client/client_argv_handling.h"
|
||||
#include "util/fuchsia/system_exception_port_key.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
CrashpadClient::CrashpadClient() {}
|
||||
|
||||
CrashpadClient::~CrashpadClient() {}
|
||||
|
||||
bool CrashpadClient::StartHandler(
|
||||
const base::FilePath& handler,
|
||||
const base::FilePath& database,
|
||||
const base::FilePath& metrics_dir,
|
||||
const std::string& url,
|
||||
const std::map<std::string, std::string>& annotations,
|
||||
const std::vector<std::string>& arguments,
|
||||
bool restartable,
|
||||
bool asynchronous_start) {
|
||||
DCHECK_EQ(restartable, false); // Not used on Fuchsia.
|
||||
DCHECK_EQ(asynchronous_start, false); // Not used on Fuchsia.
|
||||
|
||||
zx_handle_t exception_port_raw;
|
||||
zx_status_t status = zx_port_create(0, &exception_port_raw);
|
||||
if (status != ZX_OK) {
|
||||
ZX_LOG(ERROR, status) << "zx_port_create";
|
||||
return false;
|
||||
}
|
||||
base::ScopedZxHandle exception_port(exception_port_raw);
|
||||
|
||||
status = zx_task_bind_exception_port(
|
||||
zx_job_default(), exception_port.get(), kSystemExceptionPortKey, 0);
|
||||
if (status != ZX_OK) {
|
||||
ZX_LOG(ERROR, status) << "zx_task_bind_exception_port";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::string> argv_strings = BuildHandlerArgvStrings(
|
||||
handler, database, metrics_dir, url, annotations, arguments);
|
||||
|
||||
std::vector<const char*> argv;
|
||||
ConvertArgvStrings(argv_strings, &argv);
|
||||
|
||||
// Follow the same protocol as devmgr and crashlogger in Zircon (that is,
|
||||
// process handle as handle 0, with type USER0, exception port handle as
|
||||
// handle 1, also with type PA_USER0) so that it's trivial to replace
|
||||
// crashlogger with crashpad_handler. The exception port is passed on, so
|
||||
// released here. Currently it is assumed that this process's default job
|
||||
// handle is the exception port that should be monitored. In the future, it
|
||||
// might be useful for this to be configurable by the client.
|
||||
constexpr size_t kActionCount = 2;
|
||||
fdio_spawn_action_t actions[] = {
|
||||
{.action = FDIO_SPAWN_ACTION_ADD_HANDLE,
|
||||
.h = {.id = PA_HND(PA_USER0, 0), .handle = ZX_HANDLE_INVALID}},
|
||||
{.action = FDIO_SPAWN_ACTION_ADD_HANDLE,
|
||||
.h = {.id = PA_HND(PA_USER0, 1), .handle = ZX_HANDLE_INVALID}},
|
||||
};
|
||||
|
||||
status = zx_handle_duplicate(
|
||||
zx_job_default(), ZX_RIGHT_SAME_RIGHTS, &actions[0].h.handle);
|
||||
if (status != ZX_OK) {
|
||||
ZX_LOG(ERROR, status) << "zx_handle_duplicate";
|
||||
return false;
|
||||
}
|
||||
actions[1].h.handle = exception_port.release();
|
||||
|
||||
char error_message[FDIO_SPAWN_ERR_MSG_MAX_LENGTH];
|
||||
zx_handle_t child_raw;
|
||||
// TODO(scottmg): https://crashpad.chromium.org/bug/196, FDIO_SPAWN_CLONE_ALL
|
||||
// is useful during bringup, but should probably be made minimal for real
|
||||
// usage.
|
||||
status = fdio_spawn_etc(ZX_HANDLE_INVALID,
|
||||
FDIO_SPAWN_CLONE_ALL,
|
||||
argv[0],
|
||||
argv.data(),
|
||||
nullptr,
|
||||
kActionCount,
|
||||
actions,
|
||||
&child_raw,
|
||||
error_message);
|
||||
base::ScopedZxHandle child(child_raw);
|
||||
if (status != ZX_OK) {
|
||||
ZX_LOG(ERROR, status) << "fdio_spawn_etc: " << error_message;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace crashpad
|
234
client/crashpad_client_linux.cc
Normal file
234
client/crashpad_client_linux.cc
Normal file
@ -0,0 +1,234 @@
|
||||
// Copyright 2018 The Crashpad Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "client/crashpad_client.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "client/client_argv_handling.h"
|
||||
#include "util/file/file_io.h"
|
||||
#include "util/linux/exception_handler_client.h"
|
||||
#include "util/linux/exception_information.h"
|
||||
#include "util/linux/scoped_pr_set_ptracer.h"
|
||||
#include "util/misc/from_pointer_cast.h"
|
||||
#include "util/posix/double_fork_and_exec.h"
|
||||
#include "util/posix/signals.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
namespace {
|
||||
|
||||
std::string FormatArgumentInt(const std::string& name, int value) {
|
||||
return base::StringPrintf("--%s=%d", name.c_str(), value);
|
||||
}
|
||||
|
||||
std::string FormatArgumentAddress(const std::string& name, void* addr) {
|
||||
return base::StringPrintf("--%s=%p", name.c_str(), addr);
|
||||
}
|
||||
|
||||
class SignalHandler {
|
||||
public:
|
||||
virtual void HandleCrashFatal(int signo,
|
||||
siginfo_t* siginfo,
|
||||
void* context) = 0;
|
||||
virtual bool HandleCrashNonFatal(int signo,
|
||||
siginfo_t* siginfo,
|
||||
void* context) = 0;
|
||||
|
||||
void SetFirstChanceHandler(CrashpadClient::FirstChanceHandler handler) {
|
||||
first_chance_handler_ = handler;
|
||||
}
|
||||
|
||||
protected:
|
||||
SignalHandler() = default;
|
||||
~SignalHandler() = default;
|
||||
|
||||
CrashpadClient::FirstChanceHandler first_chance_handler_ = nullptr;
|
||||
};
|
||||
|
||||
// Launches a single use handler to snapshot this process.
|
||||
class LaunchAtCrashHandler : public SignalHandler {
|
||||
public:
|
||||
static LaunchAtCrashHandler* Get() {
|
||||
static LaunchAtCrashHandler* instance = new LaunchAtCrashHandler();
|
||||
return instance;
|
||||
}
|
||||
|
||||
bool Initialize(std::vector<std::string>* argv_in) {
|
||||
argv_strings_.swap(*argv_in);
|
||||
|
||||
argv_strings_.push_back(FormatArgumentAddress("trace-parent-with-exception",
|
||||
&exception_information_));
|
||||
|
||||
ConvertArgvStrings(argv_strings_, &argv_);
|
||||
return Signals::InstallCrashHandlers(HandleCrash, 0, nullptr);
|
||||
}
|
||||
|
||||
bool HandleCrashNonFatal(int signo,
|
||||
siginfo_t* siginfo,
|
||||
void* context) override {
|
||||
if (first_chance_handler_ &&
|
||||
first_chance_handler_(
|
||||
signo, siginfo, static_cast<ucontext_t*>(context))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
exception_information_.siginfo_address =
|
||||
FromPointerCast<decltype(exception_information_.siginfo_address)>(
|
||||
siginfo);
|
||||
exception_information_.context_address =
|
||||
FromPointerCast<decltype(exception_information_.context_address)>(
|
||||
context);
|
||||
exception_information_.thread_id = syscall(SYS_gettid);
|
||||
|
||||
ScopedPrSetPtracer set_ptracer(getpid(), /* may_log= */ false);
|
||||
|
||||
pid_t pid = fork();
|
||||
if (pid < 0) {
|
||||
return false;
|
||||
}
|
||||
if (pid == 0) {
|
||||
execv(argv_[0], const_cast<char* const*>(argv_.data()));
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
int status;
|
||||
waitpid(pid, &status, 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
void HandleCrashFatal(int signo, siginfo_t* siginfo, void* context) override {
|
||||
if (HandleCrashNonFatal(signo, siginfo, context)) {
|
||||
return;
|
||||
}
|
||||
Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, nullptr);
|
||||
}
|
||||
|
||||
private:
|
||||
LaunchAtCrashHandler() = default;
|
||||
|
||||
~LaunchAtCrashHandler() = delete;
|
||||
|
||||
static void HandleCrash(int signo, siginfo_t* siginfo, void* context) {
|
||||
auto state = Get();
|
||||
state->HandleCrashFatal(signo, siginfo, context);
|
||||
}
|
||||
|
||||
std::vector<std::string> argv_strings_;
|
||||
std::vector<const char*> argv_;
|
||||
ExceptionInformation exception_information_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(LaunchAtCrashHandler);
|
||||
};
|
||||
|
||||
// A pointer to the currently installed crash signal handler. This allows
|
||||
// the static method CrashpadClient::DumpWithoutCrashing to simulate a crash
|
||||
// using the currently configured crash handling strategy.
|
||||
static SignalHandler* g_crash_handler;
|
||||
|
||||
} // namespace
|
||||
|
||||
CrashpadClient::CrashpadClient() {}
|
||||
|
||||
CrashpadClient::~CrashpadClient() {}
|
||||
|
||||
bool CrashpadClient::StartHandler(
|
||||
const base::FilePath& handler,
|
||||
const base::FilePath& database,
|
||||
const base::FilePath& metrics_dir,
|
||||
const std::string& url,
|
||||
const std::map<std::string, std::string>& annotations,
|
||||
const std::vector<std::string>& arguments,
|
||||
bool restartable,
|
||||
bool asynchronous_start) {
|
||||
// TODO(jperaza): Implement this after the Android/Linux ExceptionHandlerSever
|
||||
// supports accepting new connections.
|
||||
// https://crashpad.chromium.org/bug/30
|
||||
NOTREACHED();
|
||||
return false;
|
||||
}
|
||||
|
||||
// static
|
||||
bool CrashpadClient::StartHandlerAtCrash(
|
||||
const base::FilePath& handler,
|
||||
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 = BuildHandlerArgvStrings(
|
||||
handler, database, metrics_dir, url, annotations, arguments);
|
||||
|
||||
auto signal_handler = LaunchAtCrashHandler::Get();
|
||||
if (signal_handler->Initialize(&argv)) {
|
||||
DCHECK(!g_crash_handler);
|
||||
g_crash_handler = signal_handler;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// static
|
||||
bool CrashpadClient::StartHandlerForClient(
|
||||
const base::FilePath& handler,
|
||||
const base::FilePath& database,
|
||||
const base::FilePath& metrics_dir,
|
||||
const std::string& url,
|
||||
const std::map<std::string, std::string>& annotations,
|
||||
const std::vector<std::string>& arguments,
|
||||
int socket) {
|
||||
std::vector<std::string> argv = BuildHandlerArgvStrings(
|
||||
handler, database, metrics_dir, url, annotations, arguments);
|
||||
|
||||
argv.push_back(FormatArgumentInt("initial-client", socket));
|
||||
|
||||
return DoubleForkAndExec(argv, socket, true, nullptr);
|
||||
}
|
||||
|
||||
// static
|
||||
void CrashpadClient::DumpWithoutCrash(NativeCPUContext* context) {
|
||||
DCHECK(g_crash_handler);
|
||||
|
||||
#if defined(ARCH_CPU_ARMEL)
|
||||
memset(context->uc_regspace, 0, sizeof(context->uc_regspace));
|
||||
#elif defined(ARCH_CPU_ARM64)
|
||||
memset(context->uc_mcontext.__reserved,
|
||||
0,
|
||||
sizeof(context->uc_mcontext.__reserved));
|
||||
#endif
|
||||
|
||||
siginfo_t siginfo;
|
||||
siginfo.si_signo = Signals::kSimulatedSigno;
|
||||
siginfo.si_errno = 0;
|
||||
siginfo.si_code = 0;
|
||||
g_crash_handler->HandleCrashNonFatal(
|
||||
siginfo.si_signo, &siginfo, reinterpret_cast<void*>(context));
|
||||
}
|
||||
|
||||
// static
|
||||
void CrashpadClient::SetFirstChanceExceptionHandler(
|
||||
FirstChanceHandler handler) {
|
||||
DCHECK(g_crash_handler);
|
||||
g_crash_handler->SetFirstChanceHandler(handler);
|
||||
}
|
||||
|
||||
} // namespace crashpad
|
395
client/crashpad_client_linux_test.cc
Normal file
395
client/crashpad_client_linux_test.cc
Normal file
@ -0,0 +1,395 @@
|
||||
// Copyright 2018 The Crashpad Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "client/crashpad_client.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "client/annotation.h"
|
||||
#include "client/annotation_list.h"
|
||||
#include "client/crash_report_database.h"
|
||||
#include "client/simulate_crash.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "snapshot/annotation_snapshot.h"
|
||||
#include "snapshot/minidump/process_snapshot_minidump.h"
|
||||
#include "snapshot/sanitized/sanitization_information.h"
|
||||
#include "test/multiprocess.h"
|
||||
#include "test/multiprocess_exec.h"
|
||||
#include "test/scoped_temp_dir.h"
|
||||
#include "test/test_paths.h"
|
||||
#include "util/file/file_io.h"
|
||||
#include "util/file/filesystem.h"
|
||||
#include "util/linux/exception_handler_client.h"
|
||||
#include "util/linux/exception_information.h"
|
||||
#include "util/misc/address_types.h"
|
||||
#include "util/misc/from_pointer_cast.h"
|
||||
#include "util/posix/signals.h"
|
||||
|
||||
namespace crashpad {
|
||||
namespace test {
|
||||
namespace {
|
||||
|
||||
bool HandleCrashSuccessfully(int, siginfo_t*, ucontext_t*) {
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST(CrashpadClient, SimulateCrash) {
|
||||
ScopedTempDir temp_dir;
|
||||
|
||||
base::FilePath handler_path = TestPaths::Executable().DirName().Append(
|
||||
FILE_PATH_LITERAL("crashpad_handler"));
|
||||
|
||||
crashpad::CrashpadClient client;
|
||||
ASSERT_TRUE(client.StartHandlerAtCrash(handler_path,
|
||||
base::FilePath(temp_dir.path()),
|
||||
base::FilePath(),
|
||||
"",
|
||||
std::map<std::string, std::string>(),
|
||||
std::vector<std::string>()));
|
||||
|
||||
auto database =
|
||||
CrashReportDatabase::InitializeWithoutCreating(temp_dir.path());
|
||||
ASSERT_TRUE(database);
|
||||
|
||||
{
|
||||
CrashpadClient::SetFirstChanceExceptionHandler(HandleCrashSuccessfully);
|
||||
|
||||
CRASHPAD_SIMULATE_CRASH();
|
||||
|
||||
std::vector<CrashReportDatabase::Report> reports;
|
||||
ASSERT_EQ(database->GetPendingReports(&reports),
|
||||
CrashReportDatabase::kNoError);
|
||||
EXPECT_EQ(reports.size(), 0u);
|
||||
|
||||
reports.clear();
|
||||
ASSERT_EQ(database->GetCompletedReports(&reports),
|
||||
CrashReportDatabase::kNoError);
|
||||
EXPECT_EQ(reports.size(), 0u);
|
||||
}
|
||||
|
||||
{
|
||||
CrashpadClient::SetFirstChanceExceptionHandler(nullptr);
|
||||
|
||||
CRASHPAD_SIMULATE_CRASH();
|
||||
|
||||
std::vector<CrashReportDatabase::Report> reports;
|
||||
ASSERT_EQ(database->GetPendingReports(&reports),
|
||||
CrashReportDatabase::kNoError);
|
||||
EXPECT_EQ(reports.size(), 1u);
|
||||
|
||||
reports.clear();
|
||||
ASSERT_EQ(database->GetCompletedReports(&reports),
|
||||
CrashReportDatabase::kNoError);
|
||||
EXPECT_EQ(reports.size(), 0u);
|
||||
}
|
||||
}
|
||||
|
||||
constexpr char kTestAnnotationName[] = "name_of_annotation";
|
||||
constexpr char kTestAnnotationValue[] = "value_of_annotation";
|
||||
|
||||
void ValidateDump(const CrashReportDatabase::UploadReport* report) {
|
||||
ProcessSnapshotMinidump minidump_snapshot;
|
||||
ASSERT_TRUE(minidump_snapshot.Initialize(report->Reader()));
|
||||
|
||||
for (const ModuleSnapshot* module : minidump_snapshot.Modules()) {
|
||||
for (const AnnotationSnapshot& annotation : module->AnnotationObjects()) {
|
||||
if (static_cast<Annotation::Type>(annotation.type) !=
|
||||
Annotation::Type::kString) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (annotation.name == kTestAnnotationName) {
|
||||
std::string value(
|
||||
reinterpret_cast<const char*>(annotation.value.data()),
|
||||
annotation.value.size());
|
||||
EXPECT_EQ(value, kTestAnnotationValue);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
ADD_FAILURE();
|
||||
}
|
||||
|
||||
CRASHPAD_CHILD_TEST_MAIN(StartHandlerAtCrashChild) {
|
||||
FileHandle in = StdioFileHandle(StdioStream::kStandardInput);
|
||||
|
||||
VMSize temp_dir_length;
|
||||
CheckedReadFileExactly(in, &temp_dir_length, sizeof(temp_dir_length));
|
||||
|
||||
std::string temp_dir(temp_dir_length, '\0');
|
||||
CheckedReadFileExactly(in, &temp_dir[0], temp_dir_length);
|
||||
|
||||
base::FilePath handler_path = TestPaths::Executable().DirName().Append(
|
||||
FILE_PATH_LITERAL("crashpad_handler"));
|
||||
|
||||
crashpad::AnnotationList::Register();
|
||||
|
||||
static StringAnnotation<32> test_annotation(kTestAnnotationName);
|
||||
test_annotation.Set(kTestAnnotationValue);
|
||||
|
||||
crashpad::CrashpadClient client;
|
||||
if (!client.StartHandlerAtCrash(handler_path,
|
||||
base::FilePath(temp_dir),
|
||||
base::FilePath(),
|
||||
"",
|
||||
std::map<std::string, std::string>(),
|
||||
std::vector<std::string>())) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
__builtin_trap();
|
||||
|
||||
NOTREACHED();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
class StartHandlerAtCrashTest : public MultiprocessExec {
|
||||
public:
|
||||
StartHandlerAtCrashTest() : MultiprocessExec() {
|
||||
SetChildTestMainFunction("StartHandlerAtCrashChild");
|
||||
SetExpectedChildTerminationBuiltinTrap();
|
||||
}
|
||||
|
||||
private:
|
||||
void MultiprocessParent() override {
|
||||
ScopedTempDir temp_dir;
|
||||
VMSize temp_dir_length = temp_dir.path().value().size();
|
||||
ASSERT_TRUE(LoggingWriteFile(
|
||||
WritePipeHandle(), &temp_dir_length, sizeof(temp_dir_length)));
|
||||
ASSERT_TRUE(LoggingWriteFile(
|
||||
WritePipeHandle(), temp_dir.path().value().data(), temp_dir_length));
|
||||
|
||||
// Wait for child to finish.
|
||||
CheckedReadFileAtEOF(ReadPipeHandle());
|
||||
|
||||
auto database = CrashReportDatabase::Initialize(temp_dir.path());
|
||||
ASSERT_TRUE(database);
|
||||
|
||||
std::vector<CrashReportDatabase::Report> reports;
|
||||
ASSERT_EQ(database->GetCompletedReports(&reports),
|
||||
CrashReportDatabase::kNoError);
|
||||
EXPECT_EQ(reports.size(), 0u);
|
||||
|
||||
reports.clear();
|
||||
ASSERT_EQ(database->GetPendingReports(&reports),
|
||||
CrashReportDatabase::kNoError);
|
||||
EXPECT_EQ(reports.size(), 1u);
|
||||
|
||||
std::unique_ptr<const CrashReportDatabase::UploadReport> report;
|
||||
ASSERT_EQ(database->GetReportForUploading(reports[0].uuid, &report),
|
||||
CrashReportDatabase::kNoError);
|
||||
ValidateDump(report.get());
|
||||
}
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(StartHandlerAtCrashTest);
|
||||
};
|
||||
|
||||
TEST(CrashpadClient, StartHandlerAtCrash) {
|
||||
StartHandlerAtCrashTest test;
|
||||
test.Run();
|
||||
}
|
||||
|
||||
// Test state for starting the handler for another process.
|
||||
class StartHandlerForClientTest {
|
||||
public:
|
||||
StartHandlerForClientTest() = default;
|
||||
~StartHandlerForClientTest() = default;
|
||||
|
||||
bool Initialize(bool sanitize) {
|
||||
sanitize_ = sanitize;
|
||||
|
||||
int socks[2];
|
||||
if (socketpair(AF_UNIX, SOCK_STREAM, 0, socks) != 0) {
|
||||
PLOG(ERROR) << "socketpair";
|
||||
return false;
|
||||
}
|
||||
client_sock_.reset(socks[0]);
|
||||
server_sock_.reset(socks[1]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StartHandlerOnDemand() {
|
||||
char c;
|
||||
if (!LoggingReadFileExactly(server_sock_.get(), &c, sizeof(c))) {
|
||||
ADD_FAILURE();
|
||||
return false;
|
||||
}
|
||||
|
||||
base::FilePath handler_path = TestPaths::Executable().DirName().Append(
|
||||
FILE_PATH_LITERAL("crashpad_handler"));
|
||||
|
||||
CrashpadClient client;
|
||||
if (!client.StartHandlerForClient(handler_path,
|
||||
temp_dir_.path(),
|
||||
base::FilePath(),
|
||||
"",
|
||||
std::map<std::string, std::string>(),
|
||||
std::vector<std::string>(),
|
||||
server_sock_.get())) {
|
||||
ADD_FAILURE();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ExpectReport() {
|
||||
auto database =
|
||||
CrashReportDatabase::InitializeWithoutCreating(temp_dir_.path());
|
||||
ASSERT_TRUE(database);
|
||||
|
||||
std::vector<CrashReportDatabase::Report> reports;
|
||||
ASSERT_EQ(database->GetCompletedReports(&reports),
|
||||
CrashReportDatabase::kNoError);
|
||||
EXPECT_EQ(reports.size(), 0u);
|
||||
|
||||
reports.clear();
|
||||
ASSERT_EQ(database->GetPendingReports(&reports),
|
||||
CrashReportDatabase::kNoError);
|
||||
if (sanitize_) {
|
||||
EXPECT_EQ(reports.size(), 0u);
|
||||
} else {
|
||||
EXPECT_EQ(reports.size(), 1u);
|
||||
}
|
||||
}
|
||||
|
||||
bool InstallHandler() {
|
||||
auto signal_handler = SandboxedHandler::Get();
|
||||
return signal_handler->Initialize(client_sock_.get(), sanitize_);
|
||||
}
|
||||
|
||||
private:
|
||||
// A signal handler that defers handler process startup to another, presumably
|
||||
// more privileged, process.
|
||||
class SandboxedHandler {
|
||||
public:
|
||||
static SandboxedHandler* Get() {
|
||||
static SandboxedHandler* instance = new SandboxedHandler();
|
||||
return instance;
|
||||
}
|
||||
|
||||
bool Initialize(FileHandle client_sock, bool sanitize) {
|
||||
client_sock_ = client_sock;
|
||||
sanitize_ = sanitize;
|
||||
return Signals::InstallCrashHandlers(HandleCrash, 0, nullptr);
|
||||
}
|
||||
|
||||
private:
|
||||
SandboxedHandler() = default;
|
||||
~SandboxedHandler() = delete;
|
||||
|
||||
static void HandleCrash(int signo, siginfo_t* siginfo, void* context) {
|
||||
auto state = Get();
|
||||
|
||||
char c;
|
||||
CHECK(LoggingWriteFile(state->client_sock_, &c, sizeof(c)));
|
||||
|
||||
ExceptionInformation exception_information;
|
||||
exception_information.siginfo_address =
|
||||
FromPointerCast<decltype(exception_information.siginfo_address)>(
|
||||
siginfo);
|
||||
exception_information.context_address =
|
||||
FromPointerCast<decltype(exception_information.context_address)>(
|
||||
context);
|
||||
exception_information.thread_id = syscall(SYS_gettid);
|
||||
|
||||
ClientInformation info;
|
||||
info.exception_information_address =
|
||||
FromPointerCast<decltype(info.exception_information_address)>(
|
||||
&exception_information);
|
||||
|
||||
SanitizationInformation sanitization_info = {};
|
||||
if (state->sanitize_) {
|
||||
info.sanitization_information_address =
|
||||
FromPointerCast<VMAddress>(&sanitization_info);
|
||||
// Target a non-module address to prevent a crash dump.
|
||||
sanitization_info.target_module_address =
|
||||
FromPointerCast<VMAddress>(&sanitization_info);
|
||||
}
|
||||
|
||||
ExceptionHandlerClient handler_client(state->client_sock_);
|
||||
CHECK_EQ(handler_client.RequestCrashDump(info), 0);
|
||||
|
||||
Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, nullptr);
|
||||
}
|
||||
|
||||
FileHandle client_sock_;
|
||||
bool sanitize_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(SandboxedHandler);
|
||||
};
|
||||
|
||||
ScopedTempDir temp_dir_;
|
||||
ScopedFileHandle client_sock_;
|
||||
ScopedFileHandle server_sock_;
|
||||
bool sanitize_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(StartHandlerForClientTest);
|
||||
};
|
||||
|
||||
// Tests starting the handler for a child process.
|
||||
class StartHandlerForChildTest : public Multiprocess {
|
||||
public:
|
||||
StartHandlerForChildTest() = default;
|
||||
~StartHandlerForChildTest() = default;
|
||||
|
||||
bool Initialize(bool sanitize) {
|
||||
SetExpectedChildTerminationBuiltinTrap();
|
||||
return test_state_.Initialize(sanitize);
|
||||
}
|
||||
|
||||
private:
|
||||
void MultiprocessParent() {
|
||||
ASSERT_TRUE(test_state_.StartHandlerOnDemand());
|
||||
|
||||
// Wait for chlid to finish.
|
||||
CheckedReadFileAtEOF(ReadPipeHandle());
|
||||
|
||||
test_state_.ExpectReport();
|
||||
}
|
||||
|
||||
void MultiprocessChild() {
|
||||
CHECK(test_state_.InstallHandler());
|
||||
|
||||
__builtin_trap();
|
||||
|
||||
NOTREACHED();
|
||||
}
|
||||
|
||||
StartHandlerForClientTest test_state_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(StartHandlerForChildTest);
|
||||
};
|
||||
|
||||
TEST(CrashpadClient, StartHandlerForChild) {
|
||||
StartHandlerForChildTest test;
|
||||
ASSERT_TRUE(test.Initialize(/* sanitize= */ false));
|
||||
test.Run();
|
||||
}
|
||||
|
||||
TEST(CrashpadClient, SanitizedChild) {
|
||||
StartHandlerForChildTest test;
|
||||
ASSERT_TRUE(test.Initialize(/* sanitize= */ true));
|
||||
test.Run();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace test
|
||||
} // namespace crashpad
|
@ -18,15 +18,12 @@
|
||||
#include <mach/mach.h>
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "base/mac/mach_logging.h"
|
||||
#include "base/posix/eintr_wrapper.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "util/mac/mac_util.h"
|
||||
#include "util/mach/child_port_handshake.h"
|
||||
@ -36,7 +33,7 @@
|
||||
#include "util/mach/notify_server.h"
|
||||
#include "util/misc/clock.h"
|
||||
#include "util/misc/implicit_cast.h"
|
||||
#include "util/posix/close_multiple.h"
|
||||
#include "util/posix/double_fork_and_exec.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
@ -305,9 +302,6 @@ class HandlerStarter final : public NotifyServer::DefaultInterface {
|
||||
handler_restarter->last_start_time_ = ClockMonotonicNanoseconds();
|
||||
}
|
||||
|
||||
// Set up the arguments for execve() first. These aren’t needed until
|
||||
// execve() is called, but it’s dangerous to do this in a child process
|
||||
// after fork().
|
||||
ChildPortHandshake child_port_handshake;
|
||||
base::ScopedFD server_write_fd = child_port_handshake.ServerWriteFD();
|
||||
|
||||
@ -337,110 +331,23 @@ class HandlerStarter final : public NotifyServer::DefaultInterface {
|
||||
}
|
||||
argv.push_back(FormatArgumentInt("handshake-fd", server_write_fd.get()));
|
||||
|
||||
const char* handler_c = handler.value().c_str();
|
||||
|
||||
// argv_c contains const char* pointers and is terminated by nullptr. argv
|
||||
// is required because the pointers in argv_c need to point somewhere, and
|
||||
// they can’t point to temporaries such as those returned by
|
||||
// FormatArgumentString().
|
||||
std::vector<const char*> argv_c;
|
||||
argv_c.reserve(argv.size() + 1);
|
||||
for (const std::string& argument : argv) {
|
||||
argv_c.push_back(argument.c_str());
|
||||
}
|
||||
argv_c.push_back(nullptr);
|
||||
|
||||
// Double-fork(). The three processes involved are parent, child, and
|
||||
// grandchild. The grandchild will become the handler process. The child
|
||||
// exits immediately after spawning the grandchild, so the grandchild
|
||||
// becomes an orphan and its parent process ID becomes 1. This relieves the
|
||||
// parent and child of the responsibility for reaping the grandchild with
|
||||
// waitpid() or similar. The handler process is expected to outlive the
|
||||
// parent process, so the parent shouldn’t be concerned with reaping it.
|
||||
// This approach means that accidental early termination of the handler
|
||||
// process will not result in a zombie process.
|
||||
pid_t pid = fork();
|
||||
if (pid < 0) {
|
||||
PLOG(ERROR) << "fork";
|
||||
// When restarting, reset the system default crash handler first. Otherwise,
|
||||
// the crash exception port in the handler will have been inherited from
|
||||
// this parent process, which was probably using the exception server now
|
||||
// being restarted. The handler can’t monitor itself for its own crashes via
|
||||
// this interface.
|
||||
if (!DoubleForkAndExec(
|
||||
argv,
|
||||
server_write_fd.get(),
|
||||
true,
|
||||
restart ? CrashpadClient::UseSystemDefaultHandler : nullptr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pid == 0) {
|
||||
// Child process.
|
||||
|
||||
if (restart) {
|
||||
// When restarting, reset the system default crash handler first.
|
||||
// Otherwise, the crash exception port here will have been inherited
|
||||
// from the parent process, which was probably using the exception
|
||||
// server now being restarted. The handler can’t monitor itself for its
|
||||
// own crashes via this interface.
|
||||
CrashpadClient::UseSystemDefaultHandler();
|
||||
}
|
||||
|
||||
// Call setsid(), creating a new process group and a new session, both led
|
||||
// by this process. The new process group has no controlling terminal.
|
||||
// This disconnects it from signals generated by the parent process’
|
||||
// terminal.
|
||||
//
|
||||
// setsid() is done in the child instead of the grandchild so that the
|
||||
// grandchild will not be a session leader. If it were a session leader,
|
||||
// an accidental open() of a terminal device without O_NOCTTY would make
|
||||
// that terminal the controlling terminal.
|
||||
//
|
||||
// It’s not desirable for the handler to have a controlling terminal. The
|
||||
// handler monitors clients on its own and manages its own lifetime,
|
||||
// exiting when it loses all clients and when it deems it appropraite to
|
||||
// do so. It may serve clients in different process groups or sessions
|
||||
// than its original client, and receiving signals intended for its
|
||||
// original client’s process group could be harmful in that case.
|
||||
PCHECK(setsid() != -1) << "setsid";
|
||||
|
||||
pid = fork();
|
||||
if (pid < 0) {
|
||||
PLOG(FATAL) << "fork";
|
||||
}
|
||||
|
||||
if (pid > 0) {
|
||||
// Child process.
|
||||
|
||||
// _exit() instead of exit(), because fork() was called.
|
||||
_exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
// Grandchild process.
|
||||
|
||||
CloseMultipleNowOrOnExec(STDERR_FILENO + 1, server_write_fd.get());
|
||||
|
||||
// &argv_c[0] is a pointer to a pointer to const char data, but because of
|
||||
// how C (not C++) works, execvp() wants a pointer to a const pointer to
|
||||
// char data. It modifies neither the data nor the pointers, so the
|
||||
// const_cast is safe.
|
||||
execvp(handler_c, const_cast<char* const*>(&argv_c[0]));
|
||||
PLOG(FATAL) << "execvp " << handler_c;
|
||||
}
|
||||
|
||||
// Parent process.
|
||||
|
||||
// Close the write side of the pipe, so that the handler process is the only
|
||||
// process that can write to it.
|
||||
server_write_fd.reset();
|
||||
|
||||
// waitpid() for the child, so that it does not become a zombie process. The
|
||||
// child normally exits quickly.
|
||||
int status;
|
||||
pid_t wait_pid = HANDLE_EINTR(waitpid(pid, &status, 0));
|
||||
PCHECK(wait_pid != -1) << "waitpid";
|
||||
DCHECK_EQ(wait_pid, pid);
|
||||
|
||||
if (WIFSIGNALED(status)) {
|
||||
LOG(WARNING) << "intermediate process: signal " << WTERMSIG(status);
|
||||
} else if (!WIFEXITED(status)) {
|
||||
DLOG(WARNING) << "intermediate process: unknown termination " << status;
|
||||
} else if (WEXITSTATUS(status) != EXIT_SUCCESS) {
|
||||
LOG(WARNING) << "intermediate process: exit status "
|
||||
<< WEXITSTATUS(status);
|
||||
}
|
||||
|
||||
// Rendezvous with the handler running in the grandchild process.
|
||||
if (!child_port_handshake.RunClient(receive_right.get(),
|
||||
MACH_MSG_TYPE_MOVE_RECEIVE)) {
|
||||
|
@ -30,10 +30,10 @@
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "base/synchronization/lock.h"
|
||||
#include "util/file/file_io.h"
|
||||
#include "util/misc/capture_context.h"
|
||||
#include "util/misc/from_pointer_cast.h"
|
||||
#include "util/misc/random_string.h"
|
||||
#include "util/win/address_types.h"
|
||||
#include "util/win/capture_context.h"
|
||||
#include "util/win/command_line.h"
|
||||
#include "util/win/critical_section_with_debug_info.h"
|
||||
#include "util/win/get_function.h"
|
||||
@ -750,9 +750,9 @@ void CrashpadClient::DumpWithoutCrash(const CONTEXT& context) {
|
||||
// We include a fake exception and use a code of '0x517a7ed' (something like
|
||||
// "simulated") so that it's relatively obvious in windbg that it's not
|
||||
// actually an exception. Most values in
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa363082.aspx have
|
||||
// some of the top nibble set, so we make sure to pick a value that doesn't,
|
||||
// so as to be unlikely to conflict.
|
||||
// https://msdn.microsoft.com/library/aa363082.aspx have some of the top
|
||||
// nibble set, so we make sure to pick a value that doesn't, so as to be
|
||||
// unlikely to conflict.
|
||||
constexpr uint32_t kSimulatedExceptionCode = 0x517a7ed;
|
||||
EXCEPTION_RECORD record = {};
|
||||
record.ExceptionCode = kSimulatedExceptionCode;
|
||||
@ -790,9 +790,10 @@ void CrashpadClient::DumpAndCrash(EXCEPTION_POINTERS* exception_pointers) {
|
||||
UnhandledExceptionHandler(exception_pointers);
|
||||
}
|
||||
|
||||
// static
|
||||
bool CrashpadClient::DumpAndCrashTargetProcess(HANDLE process,
|
||||
HANDLE blame_thread,
|
||||
DWORD exception_code) const {
|
||||
DWORD exception_code) {
|
||||
// Confirm we're on Vista or later.
|
||||
const DWORD version = GetVersion();
|
||||
const DWORD major_version = LOBYTE(LOWORD(version));
|
||||
|
@ -126,7 +126,13 @@ class HandlerLaunchFailureCrash : public WinMultiprocess {
|
||||
}
|
||||
};
|
||||
|
||||
TEST(CrashpadClient, HandlerLaunchFailureCrash) {
|
||||
#if defined(ADDRESS_SANITIZER)
|
||||
// https://crbug.com/845011
|
||||
#define MAYBE_HandlerLaunchFailureCrash DISABLED_HandlerLaunchFailureCrash
|
||||
#else
|
||||
#define MAYBE_HandlerLaunchFailureCrash HandlerLaunchFailureCrash
|
||||
#endif
|
||||
TEST(CrashpadClient, MAYBE_HandlerLaunchFailureCrash) {
|
||||
WinMultiprocess::Run<HandlerLaunchFailureCrash>();
|
||||
}
|
||||
|
||||
@ -150,7 +156,14 @@ class HandlerLaunchFailureDumpAndCrash : public WinMultiprocess {
|
||||
}
|
||||
};
|
||||
|
||||
TEST(CrashpadClient, HandlerLaunchFailureDumpAndCrash) {
|
||||
#if defined(ADDRESS_SANITIZER)
|
||||
// https://crbug.com/845011
|
||||
#define MAYBE_HandlerLaunchFailureDumpAndCrash \
|
||||
DISABLED_HandlerLaunchFailureDumpAndCrash
|
||||
#else
|
||||
#define MAYBE_HandlerLaunchFailureDumpAndCrash HandlerLaunchFailureDumpAndCrash
|
||||
#endif
|
||||
TEST(CrashpadClient, MAYBE_HandlerLaunchFailureDumpAndCrash) {
|
||||
WinMultiprocess::Run<HandlerLaunchFailureDumpAndCrash>();
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,11 @@
|
||||
|
||||
namespace {
|
||||
|
||||
// Don’t change this when simply adding fields. Readers will size-check the
|
||||
// structure and ignore fields they’re aware of when not present, as well as
|
||||
// fields they’re not aware of. Only change this when introducing an
|
||||
// incompatible layout, with the understanding that existing readers will not
|
||||
// understand new versions.
|
||||
constexpr uint32_t kCrashpadInfoVersion = 1;
|
||||
|
||||
} // namespace
|
||||
@ -46,18 +51,15 @@ static_assert(std::is_standard_layout<CrashpadInfo>::value,
|
||||
// This may result in a static module initializer in debug-mode builds, but
|
||||
// because it’s POD, no code should need to run to initialize this under
|
||||
// release-mode optimization.
|
||||
|
||||
#if defined(OS_POSIX)
|
||||
__attribute__((
|
||||
|
||||
#if defined(OS_MACOSX)
|
||||
// Put the structure in a well-known section name where it can be easily
|
||||
// found without having to consult the symbol table.
|
||||
#if defined(OS_MACOSX)
|
||||
section(SEG_DATA ",crashpad_info"),
|
||||
#elif defined(OS_LINUX) || defined(OS_ANDROID)
|
||||
section("crashpad_info"),
|
||||
#else // !defined(OS_MACOSX) && !defined(OS_LINUX) && !defined(OS_ANDROID)
|
||||
#error Port
|
||||
#endif // !defined(OS_MACOSX) && !defined(OS_LINUX) && !defined(OS_ANDROID)
|
||||
#endif
|
||||
|
||||
#if defined(ADDRESS_SANITIZER)
|
||||
// AddressSanitizer would add a trailing red zone of at least 32 bytes,
|
||||
@ -68,12 +70,12 @@ __attribute__((
|
||||
aligned(64),
|
||||
#endif // defined(ADDRESS_SANITIZER)
|
||||
|
||||
// The “used” attribute prevents the structure from being dead-stripped.
|
||||
used,
|
||||
|
||||
// There’s no need to expose this as a public symbol from the symbol table.
|
||||
// There's no need to expose this as a public symbol from the symbol table.
|
||||
// All accesses from the outside can locate the well-known section name.
|
||||
visibility("hidden")))
|
||||
visibility("hidden"),
|
||||
|
||||
// The “used” attribute prevents the structure from being dead-stripped.
|
||||
used))
|
||||
|
||||
#elif defined(OS_WIN)
|
||||
|
||||
@ -88,8 +90,19 @@ __declspec(allocate("CPADinfo"))
|
||||
|
||||
CrashpadInfo g_crashpad_info;
|
||||
|
||||
extern "C" int* CRASHPAD_NOTE_REFERENCE;
|
||||
|
||||
// static
|
||||
CrashpadInfo* CrashpadInfo::GetCrashpadInfo() {
|
||||
#if defined(OS_LINUX) || defined(OS_ANDROID) || defined(OS_FUCHSIA)
|
||||
// This otherwise-unused reference is used so that any module that
|
||||
// references GetCrashpadInfo() will also include the note in the
|
||||
// .note.crashpad.info section. That note in turn contains the address of
|
||||
// g_crashpad_info. This allows the module reader to find the CrashpadInfo
|
||||
// structure without requiring the use of the dynamic symbol table.
|
||||
static volatile int* pointer_to_note_section = CRASHPAD_NOTE_REFERENCE;
|
||||
(void)pointer_to_note_section;
|
||||
#endif
|
||||
return &g_crashpad_info;
|
||||
}
|
||||
|
||||
@ -106,13 +119,7 @@ CrashpadInfo::CrashpadInfo()
|
||||
extra_memory_ranges_(nullptr),
|
||||
simple_annotations_(nullptr),
|
||||
user_data_minidump_stream_head_(nullptr),
|
||||
annotations_list_(nullptr)
|
||||
#if !defined(NDEBUG) && defined(OS_WIN)
|
||||
,
|
||||
invalid_read_detection_(0xbadc0de)
|
||||
#endif
|
||||
{
|
||||
}
|
||||
annotations_list_(nullptr) {}
|
||||
|
||||
void CrashpadInfo::AddUserDataMinidumpStream(uint32_t stream_type,
|
||||
const void* data,
|
||||
|
@ -233,10 +233,10 @@ struct CrashpadInfo {
|
||||
#pragma clang diagnostic ignored "-Wunused-private-field"
|
||||
#endif
|
||||
|
||||
// Fields present in version 1:
|
||||
// Fields present in version 1, subject to a check of the size_ field:
|
||||
uint32_t signature_; // kSignature
|
||||
uint32_t size_; // The size of the entire CrashpadInfo structure.
|
||||
uint32_t version_; // kVersion
|
||||
uint32_t version_; // kCrashpadInfoVersion
|
||||
uint32_t indirectly_referenced_memory_cap_;
|
||||
uint32_t padding_0_;
|
||||
TriState crashpad_handler_behavior_;
|
||||
@ -248,9 +248,11 @@ struct CrashpadInfo {
|
||||
internal::UserDataMinidumpStreamListEntry* user_data_minidump_stream_head_;
|
||||
AnnotationList* annotations_list_; // weak
|
||||
|
||||
#if !defined(NDEBUG) && defined(OS_WIN)
|
||||
uint32_t invalid_read_detection_;
|
||||
#endif
|
||||
// It’s generally safe to add new fields without changing
|
||||
// kCrashpadInfoVersion, because readers should check size_ and ignore fields
|
||||
// that aren’t present, as well as unknown fields.
|
||||
//
|
||||
// Adding fields? Consider snapshot/crashpad_info_size_test_module.cc too.
|
||||
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic pop
|
||||
|
59
client/crashpad_info_note.S
Normal file
59
client/crashpad_info_note.S
Normal file
@ -0,0 +1,59 @@
|
||||
// Copyright 2018 The Crashpad Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// This note section is used on ELF platforms to give ElfImageReader a method
|
||||
// of finding the instance of CrashpadInfo g_crashpad_info without requiring
|
||||
// that symbol to be in the dynamic symbol table.
|
||||
|
||||
#include "util/misc/elf_note_types.h"
|
||||
|
||||
// namespace crashpad {
|
||||
// CrashpadInfo g_crashpad_info;
|
||||
// } // namespace crashpad
|
||||
#define CRASHPAD_INFO_SYMBOL _ZN8crashpad15g_crashpad_infoE
|
||||
|
||||
#define NOTE_ALIGN 4
|
||||
|
||||
// This section must be "a"llocated so that it appears in the final binary at
|
||||
// runtime, and "w"ritable so that the relocation to CRASHPAD_INFO_SYMBOL can
|
||||
// be performed.
|
||||
.section .note.crashpad.info,"aw",%note
|
||||
.balign NOTE_ALIGN
|
||||
# .globl indicates that it's available to link against other .o files. .hidden
|
||||
# indicates that it will not appear in the executable's symbol table.
|
||||
.globl CRASHPAD_NOTE_REFERENCE
|
||||
.hidden CRASHPAD_NOTE_REFERENCE
|
||||
.type CRASHPAD_NOTE_REFERENCE, %object
|
||||
CRASHPAD_NOTE_REFERENCE:
|
||||
.long name_end - name // namesz
|
||||
.long desc_end - desc // descsz
|
||||
.long CRASHPAD_ELF_NOTE_TYPE_CRASHPAD_INFO // type
|
||||
name:
|
||||
.asciz CRASHPAD_ELF_NOTE_NAME
|
||||
name_end:
|
||||
.balign NOTE_ALIGN
|
||||
desc:
|
||||
#if defined(__LP64__)
|
||||
.quad CRASHPAD_INFO_SYMBOL
|
||||
#else
|
||||
#if defined(__LITTLE_ENDIAN__)
|
||||
.long CRASHPAD_INFO_SYMBOL
|
||||
.long 0
|
||||
#else
|
||||
.long 0
|
||||
.long CRASHPAD_INFO_SYMBOL
|
||||
#endif // __LITTLE_ENDIAN__
|
||||
#endif // __LP64__
|
||||
desc_end:
|
||||
.size CRASHPAD_NOTE_REFERENCE, .-CRASHPAD_NOTE_REFERENCE
|
@ -36,20 +36,27 @@ class MockDatabase : public CrashReportDatabase {
|
||||
public:
|
||||
// CrashReportDatabase:
|
||||
MOCK_METHOD0(GetSettings, Settings*());
|
||||
MOCK_METHOD1(PrepareNewCrashReport, OperationStatus(NewReport**));
|
||||
MOCK_METHOD2(FinishedWritingCrashReport, OperationStatus(NewReport*, UUID*));
|
||||
MOCK_METHOD1(ErrorWritingCrashReport, OperationStatus(NewReport*));
|
||||
MOCK_METHOD1(PrepareNewCrashReport,
|
||||
OperationStatus(std::unique_ptr<NewReport>*));
|
||||
MOCK_METHOD2(LookUpCrashReport, OperationStatus(const UUID&, Report*));
|
||||
MOCK_METHOD1(GetPendingReports, OperationStatus(std::vector<Report>*));
|
||||
MOCK_METHOD1(GetCompletedReports, OperationStatus(std::vector<Report>*));
|
||||
MOCK_METHOD2(GetReportForUploading,
|
||||
OperationStatus(const UUID&, const Report**));
|
||||
OperationStatus(const UUID&,
|
||||
std::unique_ptr<const UploadReport>*));
|
||||
MOCK_METHOD3(RecordUploadAttempt,
|
||||
OperationStatus(const Report*, bool, const std::string&));
|
||||
OperationStatus(UploadReport*, bool, const std::string&));
|
||||
MOCK_METHOD2(SkipReportUpload,
|
||||
OperationStatus(const UUID&, Metrics::CrashSkippedReason));
|
||||
MOCK_METHOD1(DeleteReport, OperationStatus(const UUID&));
|
||||
MOCK_METHOD1(RequestUpload, OperationStatus(const UUID&));
|
||||
|
||||
// gmock doesn't support mocking methods with non-copyable types such as
|
||||
// unique_ptr.
|
||||
OperationStatus FinishedWritingCrashReport(std::unique_ptr<NewReport> report,
|
||||
UUID* uuid) override {
|
||||
return kNoError;
|
||||
}
|
||||
};
|
||||
|
||||
time_t NDaysAgo(int num_days) {
|
||||
|
@ -20,10 +20,55 @@
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "base/posix/eintr_wrapper.h"
|
||||
#include "util/file/filesystem.h"
|
||||
#include "util/numeric/in_range_cast.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
#if defined(OS_FUCHSIA)
|
||||
|
||||
Settings::ScopedLockedFileHandle::ScopedLockedFileHandle()
|
||||
: handle_(kInvalidFileHandle), lockfile_path_() {
|
||||
}
|
||||
|
||||
Settings::ScopedLockedFileHandle::ScopedLockedFileHandle(
|
||||
FileHandle handle,
|
||||
const base::FilePath& lockfile_path)
|
||||
: handle_(handle), lockfile_path_(lockfile_path) {
|
||||
}
|
||||
|
||||
Settings::ScopedLockedFileHandle::ScopedLockedFileHandle(
|
||||
ScopedLockedFileHandle&& other)
|
||||
: handle_(other.handle_), lockfile_path_(other.lockfile_path_) {
|
||||
other.handle_ = kInvalidFileHandle;
|
||||
other.lockfile_path_ = base::FilePath();
|
||||
}
|
||||
|
||||
Settings::ScopedLockedFileHandle& Settings::ScopedLockedFileHandle::operator=(
|
||||
ScopedLockedFileHandle&& other) {
|
||||
handle_ = other.handle_;
|
||||
lockfile_path_ = other.lockfile_path_;
|
||||
|
||||
other.handle_ = kInvalidFileHandle;
|
||||
other.lockfile_path_ = base::FilePath();
|
||||
return *this;
|
||||
}
|
||||
|
||||
Settings::ScopedLockedFileHandle::~ScopedLockedFileHandle() {
|
||||
Destroy();
|
||||
}
|
||||
|
||||
void Settings::ScopedLockedFileHandle::Destroy() {
|
||||
if (handle_ != kInvalidFileHandle) {
|
||||
CheckedCloseFile(handle_);
|
||||
}
|
||||
if (!lockfile_path_.empty()) {
|
||||
DCHECK(LoggingRemoveFile(lockfile_path_));
|
||||
}
|
||||
}
|
||||
|
||||
#else // OS_FUCHSIA
|
||||
|
||||
namespace internal {
|
||||
|
||||
// static
|
||||
@ -36,6 +81,8 @@ void ScopedLockedFileHandleTraits::Free(FileHandle handle) {
|
||||
|
||||
} // namespace internal
|
||||
|
||||
#endif // OS_FUCHSIA
|
||||
|
||||
struct Settings::Data {
|
||||
static const uint32_t kSettingsMagic = 'CPds';
|
||||
static const uint32_t kSettingsVersion = 1;
|
||||
@ -59,16 +106,14 @@ struct Settings::Data {
|
||||
UUID client_id;
|
||||
};
|
||||
|
||||
Settings::Settings(const base::FilePath& file_path)
|
||||
: file_path_(file_path),
|
||||
initialized_() {
|
||||
}
|
||||
Settings::Settings() = default;
|
||||
|
||||
Settings::~Settings() {
|
||||
}
|
||||
Settings::~Settings() = default;
|
||||
|
||||
bool Settings::Initialize() {
|
||||
bool Settings::Initialize(const base::FilePath& file_path) {
|
||||
DCHECK(initialized_.is_uninitialized());
|
||||
initialized_.set_invalid();
|
||||
file_path_ = file_path;
|
||||
|
||||
Data settings;
|
||||
if (!OpenForWritingAndReadSettings(&settings).is_valid())
|
||||
@ -144,18 +189,33 @@ bool Settings::SetLastUploadAttemptTime(time_t time) {
|
||||
// static
|
||||
Settings::ScopedLockedFileHandle Settings::MakeScopedLockedFileHandle(
|
||||
FileHandle file,
|
||||
FileLocking locking) {
|
||||
FileLocking locking,
|
||||
const base::FilePath& file_path) {
|
||||
ScopedFileHandle scoped(file);
|
||||
#if defined(OS_FUCHSIA)
|
||||
base::FilePath lockfile_path(file_path.value() + ".__lock__");
|
||||
if (scoped.is_valid()) {
|
||||
ScopedFileHandle lockfile_scoped(
|
||||
LoggingOpenFileForWrite(lockfile_path,
|
||||
FileWriteMode::kCreateOrFail,
|
||||
FilePermissions::kWorldReadable));
|
||||
// This is a lightweight attempt to try to catch racy behavior.
|
||||
DCHECK(lockfile_scoped.is_valid());
|
||||
return ScopedLockedFileHandle(scoped.release(), lockfile_path);
|
||||
}
|
||||
return ScopedLockedFileHandle(scoped.release(), base::FilePath());
|
||||
#else
|
||||
if (scoped.is_valid()) {
|
||||
if (!LoggingLockFile(scoped.get(), locking))
|
||||
scoped.reset();
|
||||
}
|
||||
return ScopedLockedFileHandle(scoped.release());
|
||||
#endif
|
||||
}
|
||||
|
||||
Settings::ScopedLockedFileHandle Settings::OpenForReading() {
|
||||
return MakeScopedLockedFileHandle(LoggingOpenFileForRead(file_path()),
|
||||
FileLocking::kShared);
|
||||
return MakeScopedLockedFileHandle(
|
||||
LoggingOpenFileForRead(file_path()), FileLocking::kShared, file_path());
|
||||
}
|
||||
|
||||
Settings::ScopedLockedFileHandle Settings::OpenForReadingAndWriting(
|
||||
@ -171,7 +231,8 @@ Settings::ScopedLockedFileHandle Settings::OpenForReadingAndWriting(
|
||||
file_path(), mode, FilePermissions::kWorldReadable);
|
||||
}
|
||||
|
||||
return MakeScopedLockedFileHandle(handle, FileLocking::kExclusive);
|
||||
return MakeScopedLockedFileHandle(
|
||||
handle, FileLocking::kExclusive, file_path());
|
||||
}
|
||||
|
||||
bool Settings::OpenAndReadSettings(Data* out_data) {
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/macros.h"
|
||||
#include "base/scoped_generic.h"
|
||||
#include "build/build_config.h"
|
||||
#include "util/file/file_io.h"
|
||||
#include "util/misc/initialization_state.h"
|
||||
#include "util/misc/uuid.h"
|
||||
@ -44,10 +45,18 @@ struct ScopedLockedFileHandleTraits {
|
||||
//! should be retrieved via CrashReportDatabase::GetSettings().
|
||||
class Settings {
|
||||
public:
|
||||
explicit Settings(const base::FilePath& file_path);
|
||||
Settings();
|
||||
~Settings();
|
||||
|
||||
bool Initialize();
|
||||
//! \brief Initializes the settings data store.
|
||||
//!
|
||||
//! This method must be called only once, and must be successfully called
|
||||
//! before any other method in this class may be called.
|
||||
//!
|
||||
//! \param[in] path The location to store the settings data.
|
||||
//! \return `true` if the data store was initialized successfully, otherwise
|
||||
//! `false` with an error logged.
|
||||
bool Initialize(const base::FilePath& path);
|
||||
|
||||
//! \brief Retrieves the immutable identifier for this client, which is used
|
||||
//! on a server to locate all crash reports from a specific Crashpad
|
||||
@ -106,11 +115,45 @@ class Settings {
|
||||
struct Data;
|
||||
|
||||
// This must be constructed with MakeScopedLockedFileHandle(). It both unlocks
|
||||
// and closes the file on destruction.
|
||||
// and closes the file on destruction. Note that on Fuchsia, this handle DOES
|
||||
// NOT offer correct operation, only an attempt to DCHECK if racy behavior is
|
||||
// detected.
|
||||
#if defined(OS_FUCHSIA)
|
||||
struct ScopedLockedFileHandle {
|
||||
public:
|
||||
ScopedLockedFileHandle();
|
||||
ScopedLockedFileHandle(FileHandle handle,
|
||||
const base::FilePath& lockfile_path);
|
||||
ScopedLockedFileHandle(ScopedLockedFileHandle&& other);
|
||||
ScopedLockedFileHandle& operator=(ScopedLockedFileHandle&& other);
|
||||
~ScopedLockedFileHandle();
|
||||
|
||||
// These mirror the non-Fuchsia ScopedLockedFileHandle via ScopedGeneric so
|
||||
// that calling code can pretend this implementation is the same.
|
||||
bool is_valid() const { return handle_ != kInvalidFileHandle; }
|
||||
FileHandle get() { return handle_; }
|
||||
void reset() {
|
||||
Destroy();
|
||||
handle_ = kInvalidFileHandle;
|
||||
lockfile_path_ = base::FilePath();
|
||||
}
|
||||
|
||||
private:
|
||||
void Destroy();
|
||||
|
||||
FileHandle handle_;
|
||||
base::FilePath lockfile_path_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ScopedLockedFileHandle);
|
||||
};
|
||||
#else // OS_FUCHSIA
|
||||
using ScopedLockedFileHandle =
|
||||
base::ScopedGeneric<FileHandle, internal::ScopedLockedFileHandleTraits>;
|
||||
static ScopedLockedFileHandle MakeScopedLockedFileHandle(FileHandle file,
|
||||
FileLocking locking);
|
||||
#endif // OS_FUCHSIA
|
||||
static ScopedLockedFileHandle MakeScopedLockedFileHandle(
|
||||
FileHandle file,
|
||||
FileLocking locking,
|
||||
const base::FilePath& file_path);
|
||||
|
||||
// Opens the settings file for reading. On error, logs a message and returns
|
||||
// the invalid handle.
|
||||
|
@ -26,7 +26,7 @@ namespace {
|
||||
|
||||
class SettingsTest : public testing::Test {
|
||||
public:
|
||||
SettingsTest() : settings_(settings_path()) {}
|
||||
SettingsTest() = default;
|
||||
|
||||
base::FilePath settings_path() {
|
||||
return temp_dir_.path().Append(FILE_PATH_LITERAL("settings"));
|
||||
@ -49,7 +49,7 @@ class SettingsTest : public testing::Test {
|
||||
protected:
|
||||
// testing::Test:
|
||||
void SetUp() override {
|
||||
ASSERT_TRUE(settings()->Initialize());
|
||||
ASSERT_TRUE(settings()->Initialize(settings_path()));
|
||||
}
|
||||
|
||||
private:
|
||||
@ -64,8 +64,8 @@ TEST_F(SettingsTest, ClientID) {
|
||||
EXPECT_TRUE(settings()->GetClientID(&client_id));
|
||||
EXPECT_NE(client_id, UUID());
|
||||
|
||||
Settings local_settings(settings_path());
|
||||
EXPECT_TRUE(local_settings.Initialize());
|
||||
Settings local_settings;
|
||||
EXPECT_TRUE(local_settings.Initialize(settings_path()));
|
||||
UUID actual;
|
||||
EXPECT_TRUE(local_settings.GetClientID(&actual));
|
||||
EXPECT_EQ(actual, client_id);
|
||||
@ -81,8 +81,8 @@ TEST_F(SettingsTest, UploadsEnabled) {
|
||||
EXPECT_TRUE(settings()->GetUploadsEnabled(&enabled));
|
||||
EXPECT_TRUE(enabled);
|
||||
|
||||
Settings local_settings(settings_path());
|
||||
EXPECT_TRUE(local_settings.Initialize());
|
||||
Settings local_settings;
|
||||
EXPECT_TRUE(local_settings.Initialize(settings_path()));
|
||||
enabled = false;
|
||||
EXPECT_TRUE(local_settings.GetUploadsEnabled(&enabled));
|
||||
EXPECT_TRUE(enabled);
|
||||
@ -107,8 +107,8 @@ TEST_F(SettingsTest, LastUploadAttemptTime) {
|
||||
EXPECT_TRUE(settings()->GetLastUploadAttemptTime(&actual));
|
||||
EXPECT_EQ(actual, expected);
|
||||
|
||||
Settings local_settings(settings_path());
|
||||
EXPECT_TRUE(local_settings.Initialize());
|
||||
Settings local_settings;
|
||||
EXPECT_TRUE(local_settings.Initialize(settings_path()));
|
||||
actual = -1;
|
||||
EXPECT_TRUE(local_settings.GetLastUploadAttemptTime(&actual));
|
||||
EXPECT_EQ(actual, expected);
|
||||
@ -120,8 +120,8 @@ TEST_F(SettingsTest, LastUploadAttemptTime) {
|
||||
TEST_F(SettingsTest, BadFileOnInitialize) {
|
||||
InitializeBadFile();
|
||||
|
||||
Settings settings(settings_path());
|
||||
EXPECT_TRUE(settings.Initialize());
|
||||
Settings settings;
|
||||
EXPECT_TRUE(settings.Initialize(settings_path()));
|
||||
}
|
||||
|
||||
TEST_F(SettingsTest, BadFileOnGet) {
|
||||
@ -131,8 +131,8 @@ TEST_F(SettingsTest, BadFileOnGet) {
|
||||
EXPECT_TRUE(settings()->GetClientID(&client_id));
|
||||
EXPECT_NE(client_id, UUID());
|
||||
|
||||
Settings local_settings(settings_path());
|
||||
EXPECT_TRUE(local_settings.Initialize());
|
||||
Settings local_settings;
|
||||
EXPECT_TRUE(local_settings.Initialize(settings_path()));
|
||||
UUID actual;
|
||||
EXPECT_TRUE(local_settings.GetClientID(&actual));
|
||||
EXPECT_EQ(actual, client_id);
|
||||
@ -161,8 +161,8 @@ TEST_F(SettingsTest, UnlinkFile) {
|
||||
<< ErrnoMessage("unlink");
|
||||
#endif
|
||||
|
||||
Settings local_settings(settings_path());
|
||||
EXPECT_TRUE(local_settings.Initialize());
|
||||
Settings local_settings;
|
||||
EXPECT_TRUE(local_settings.Initialize(settings_path()));
|
||||
UUID new_client_id;
|
||||
EXPECT_TRUE(local_settings.GetClientID(&new_client_id));
|
||||
EXPECT_NE(new_client_id, client_id);
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "test/gtest_death_check.h"
|
||||
#include "test/gtest_death.h"
|
||||
|
||||
namespace crashpad {
|
||||
namespace test {
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "test/gtest_death_check.h"
|
||||
#include "test/gtest_death.h"
|
||||
|
||||
namespace crashpad {
|
||||
namespace test {
|
||||
@ -253,21 +253,30 @@ TEST(SimpleStringDictionary, OutOfSpace) {
|
||||
|
||||
#if DCHECK_IS_ON()
|
||||
|
||||
TEST(SimpleStringDictionaryDeathTest, NullKey) {
|
||||
TEST(SimpleStringDictionaryDeathTest, SetKeyValueWithNullKey) {
|
||||
TSimpleStringDictionary<4, 6, 6> map;
|
||||
ASSERT_DEATH_CHECK(map.SetKeyValue(nullptr, "hello"), "key");
|
||||
}
|
||||
|
||||
TEST(SimpleStringDictionaryDeathTest, GetValueForKeyWithNullKey) {
|
||||
TSimpleStringDictionary<4, 6, 6> map;
|
||||
map.SetKeyValue("hi", "there");
|
||||
ASSERT_DEATH_CHECK(map.GetValueForKey(nullptr), "key");
|
||||
EXPECT_STREQ("there", map.GetValueForKey("hi"));
|
||||
|
||||
ASSERT_DEATH_CHECK(map.GetValueForKey(nullptr), "key");
|
||||
map.RemoveKey("hi");
|
||||
EXPECT_EQ(map.GetCount(), 0u);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// The tests above, without DEATH_CHECK assertions.
|
||||
TEST(SimpleStringDictionaryDeathTest, GetValueForKeyWithoutNullKey) {
|
||||
TSimpleStringDictionary<4, 6, 6> map;
|
||||
|
||||
map.SetKeyValue("hi", "there");
|
||||
EXPECT_STREQ("there", map.GetValueForKey("hi"));
|
||||
map.RemoveKey("hi");
|
||||
EXPECT_EQ(map.GetCount(), 0u);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace test
|
||||
} // namespace crashpad
|
||||
|
@ -21,6 +21,8 @@
|
||||
#include "client/simulate_crash_mac.h"
|
||||
#elif defined(OS_WIN)
|
||||
#include "client/simulate_crash_win.h"
|
||||
#elif defined(OS_LINUX) || defined(OS_ANDROID)
|
||||
#include "client/simulate_crash_linux.h"
|
||||
#endif
|
||||
|
||||
#endif // CRASHPAD_CLIENT_SIMULATE_CRASH_H_
|
||||
|
31
client/simulate_crash_linux.h
Normal file
31
client/simulate_crash_linux.h
Normal file
@ -0,0 +1,31 @@
|
||||
// Copyright 2018 The Crashpad Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef CRASHPAD_CLIENT_SIMULATE_CRASH_LINUX_H_
|
||||
#define CRASHPAD_CLIENT_SIMULATE_CRASH_LINUX_H_
|
||||
|
||||
#include "client/crashpad_client.h"
|
||||
#include "util/misc/capture_context.h"
|
||||
|
||||
//! \file
|
||||
|
||||
//! \brief Captures the CPU context and simulates an exception without crashing.
|
||||
#define CRASHPAD_SIMULATE_CRASH() \
|
||||
do { \
|
||||
crashpad::NativeCPUContext simulate_crash_cpu_context; \
|
||||
crashpad::CaptureContext(&simulate_crash_cpu_context); \
|
||||
crashpad::CrashpadClient::DumpWithoutCrash(&simulate_crash_cpu_context); \
|
||||
} while (false)
|
||||
|
||||
#endif // CRASHPAD_CLIENT_SIMULATE_CRASH_LINUX_H_
|
@ -17,7 +17,7 @@
|
||||
|
||||
#include <mach/mach.h>
|
||||
|
||||
#include "client/capture_context_mac.h"
|
||||
#include "util/misc/capture_context.h"
|
||||
|
||||
//! \file
|
||||
|
||||
|
@ -18,16 +18,17 @@
|
||||
#include <windows.h>
|
||||
|
||||
#include "client/crashpad_client.h"
|
||||
#include "util/win/capture_context.h"
|
||||
#include "util/misc/capture_context.h"
|
||||
|
||||
//! \file
|
||||
|
||||
//! \brief Captures the CPU context and captures a dump without an exception.
|
||||
#define CRASHPAD_SIMULATE_CRASH() \
|
||||
do { \
|
||||
CONTEXT context; \
|
||||
crashpad::CaptureContext(&context); \
|
||||
crashpad::CrashpadClient::DumpWithoutCrash(context); \
|
||||
#define CRASHPAD_SIMULATE_CRASH() \
|
||||
do { \
|
||||
/* Not "context" to avoid variable shadowing warnings. */ \
|
||||
CONTEXT simulate_crash_cpu_context; \
|
||||
crashpad::CaptureContext(&simulate_crash_cpu_context); \
|
||||
crashpad::CrashpadClient::DumpWithoutCrash(simulate_crash_cpu_context); \
|
||||
} while (false)
|
||||
|
||||
#endif // CRASHPAD_CLIENT_SIMULATE_CRASH_WIN_H_
|
||||
|
127
compat/BUILD.gn
Normal file
127
compat/BUILD.gn
Normal file
@ -0,0 +1,127 @@
|
||||
# Copyright 2015 The Crashpad Authors. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import("../build/crashpad_buildconfig.gni")
|
||||
|
||||
config("compat_config") {
|
||||
include_dirs = []
|
||||
|
||||
if (crashpad_is_mac) {
|
||||
include_dirs += [ "mac" ]
|
||||
}
|
||||
|
||||
if (crashpad_is_linux || crashpad_is_android) {
|
||||
include_dirs += [ "linux" ]
|
||||
}
|
||||
|
||||
if (crashpad_is_android) {
|
||||
include_dirs += [ "android" ]
|
||||
}
|
||||
|
||||
if (crashpad_is_win) {
|
||||
include_dirs += [ "win" ]
|
||||
} else {
|
||||
include_dirs += [ "non_win" ]
|
||||
}
|
||||
}
|
||||
|
||||
template("compat_target") {
|
||||
if (crashpad_is_mac) {
|
||||
# There are no sources to compile, which doesn’t mix will with a
|
||||
# static_library.
|
||||
group(target_name) {
|
||||
forward_variables_from(invoker, "*")
|
||||
}
|
||||
} else {
|
||||
static_library(target_name) {
|
||||
forward_variables_from(invoker, "*")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
compat_target("compat") {
|
||||
sources = []
|
||||
|
||||
if (crashpad_is_mac) {
|
||||
sources += [
|
||||
"mac/AvailabilityMacros.h",
|
||||
"mac/kern/exc_resource.h",
|
||||
"mac/mach-o/loader.h",
|
||||
"mac/mach/mach.h",
|
||||
"mac/sys/resource.h",
|
||||
]
|
||||
} else {
|
||||
sources += [ "non_mac/mach/mach.h" ]
|
||||
}
|
||||
|
||||
if (crashpad_is_linux || crashpad_is_android) {
|
||||
sources += [
|
||||
"linux/signal.h",
|
||||
"linux/sys/ptrace.h",
|
||||
"linux/sys/user.h",
|
||||
]
|
||||
}
|
||||
|
||||
if (crashpad_is_android) {
|
||||
sources += [
|
||||
"android/dlfcn_internal.cc",
|
||||
"android/dlfcn_internal.h",
|
||||
"android/elf.h",
|
||||
"android/linux/elf.h",
|
||||
"android/linux/prctl.h",
|
||||
"android/linux/ptrace.h",
|
||||
"android/sched.h",
|
||||
"android/sys/epoll.cc",
|
||||
"android/sys/epoll.h",
|
||||
"android/sys/mman.cc",
|
||||
"android/sys/mman.h",
|
||||
"android/sys/syscall.h",
|
||||
"android/sys/user.h",
|
||||
]
|
||||
}
|
||||
|
||||
if (crashpad_is_win) {
|
||||
sources += [
|
||||
"win/getopt.h",
|
||||
"win/strings.cc",
|
||||
"win/strings.h",
|
||||
"win/sys/types.h",
|
||||
"win/time.cc",
|
||||
"win/time.h",
|
||||
"win/winbase.h",
|
||||
"win/winnt.h",
|
||||
"win/winternl.h",
|
||||
]
|
||||
} else {
|
||||
sources += [
|
||||
"non_win/dbghelp.h",
|
||||
"non_win/minwinbase.h",
|
||||
"non_win/timezoneapi.h",
|
||||
"non_win/verrsrc.h",
|
||||
"non_win/windows.h",
|
||||
"non_win/winnt.h",
|
||||
]
|
||||
}
|
||||
|
||||
public_configs = [
|
||||
":compat_config",
|
||||
"..:crashpad_config",
|
||||
]
|
||||
|
||||
deps = []
|
||||
|
||||
if (crashpad_is_win) {
|
||||
deps += [ "../third_party/getopt" ]
|
||||
}
|
||||
}
|
166
compat/android/dlfcn_internal.cc
Normal file
166
compat/android/dlfcn_internal.cc
Normal file
@ -0,0 +1,166 @@
|
||||
// Copyright 2017 The Crashpad Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "dlfcn_internal.h"
|
||||
|
||||
#include <android/api-level.h>
|
||||
#include <dlfcn.h>
|
||||
#include <errno.h>
|
||||
#include <setjmp.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/system_properties.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <mutex>
|
||||
|
||||
namespace crashpad {
|
||||
namespace internal {
|
||||
|
||||
// KitKat supports API levels up to 20.
|
||||
#if __ANDROID_API__ < 21
|
||||
|
||||
namespace {
|
||||
|
||||
class ScopedSigactionRestore {
|
||||
public:
|
||||
ScopedSigactionRestore() : old_action_(), signo_(-1), valid_(false) {}
|
||||
|
||||
~ScopedSigactionRestore() { Reset(); }
|
||||
|
||||
bool Reset() {
|
||||
bool result = true;
|
||||
if (valid_) {
|
||||
result = sigaction(signo_, &old_action_, nullptr) == 0;
|
||||
if (!result) {
|
||||
PrintErrmsg(errno);
|
||||
}
|
||||
}
|
||||
valid_ = false;
|
||||
signo_ = -1;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool ResetAndInstallHandler(int signo,
|
||||
void (*handler)(int, siginfo_t*, void*)) {
|
||||
Reset();
|
||||
|
||||
struct sigaction act;
|
||||
act.sa_sigaction = handler;
|
||||
sigemptyset(&act.sa_mask);
|
||||
act.sa_flags = SA_SIGINFO;
|
||||
if (sigaction(signo, &act, &old_action_) != 0) {
|
||||
PrintErrmsg(errno);
|
||||
return false;
|
||||
}
|
||||
signo_ = signo;
|
||||
valid_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
void PrintErrmsg(int err) {
|
||||
char errmsg[256];
|
||||
|
||||
if (strerror_r(err, errmsg, sizeof(errmsg)) != 0) {
|
||||
snprintf(errmsg,
|
||||
sizeof(errmsg),
|
||||
"%s:%d: Couldn't set errmsg for %d: %d",
|
||||
__FILE__,
|
||||
__LINE__,
|
||||
err,
|
||||
errno);
|
||||
return;
|
||||
}
|
||||
|
||||
fprintf(stderr, "%s:%d: sigaction: %s", __FILE__, __LINE__, errmsg);
|
||||
}
|
||||
|
||||
struct sigaction old_action_;
|
||||
int signo_;
|
||||
bool valid_;
|
||||
};
|
||||
|
||||
bool IsKitKat() {
|
||||
char prop_buf[PROP_VALUE_MAX];
|
||||
int length = __system_property_get("ro.build.version.sdk", prop_buf);
|
||||
if (length <= 0) {
|
||||
fprintf(stderr, "%s:%d: Couldn't get version", __FILE__, __LINE__);
|
||||
// It's safer to assume this is KitKat and execute dlsym with a signal
|
||||
// handler installed.
|
||||
return true;
|
||||
}
|
||||
if (strcmp(prop_buf, "19") == 0 || strcmp(prop_buf, "20") == 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
class ScopedSetTID {
|
||||
public:
|
||||
explicit ScopedSetTID(pid_t* tid) : tid_(tid) { *tid_ = syscall(SYS_gettid); }
|
||||
|
||||
~ScopedSetTID() { *tid_ = -1; }
|
||||
|
||||
private:
|
||||
pid_t* tid_;
|
||||
};
|
||||
|
||||
sigjmp_buf dlsym_sigjmp_env;
|
||||
|
||||
pid_t dlsym_tid = -1;
|
||||
|
||||
void HandleSIGFPE(int signo, siginfo_t* siginfo, void* context) {
|
||||
if (siginfo->si_code != FPE_INTDIV || syscall(SYS_gettid) != dlsym_tid) {
|
||||
return;
|
||||
}
|
||||
siglongjmp(dlsym_sigjmp_env, 1);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void* Dlsym(void* handle, const char* symbol) {
|
||||
if (!IsKitKat()) {
|
||||
return dlsym(handle, symbol);
|
||||
}
|
||||
|
||||
static std::mutex* signal_handler_mutex = new std::mutex();
|
||||
std::lock_guard<std::mutex> lock(*signal_handler_mutex);
|
||||
|
||||
ScopedSetTID set_tid(&dlsym_tid);
|
||||
|
||||
ScopedSigactionRestore sig_restore;
|
||||
if (!sig_restore.ResetAndInstallHandler(SIGFPE, HandleSIGFPE)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (sigsetjmp(dlsym_sigjmp_env, 1) != 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return dlsym(handle, symbol);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void* Dlsym(void* handle, const char* symbol) {
|
||||
return dlsym(handle, symbol);
|
||||
}
|
||||
|
||||
#endif // __ANDROID_API__ < 21
|
||||
|
||||
} // namespace internal
|
||||
} // namespace crashpad
|
35
compat/android/dlfcn_internal.h
Normal file
35
compat/android/dlfcn_internal.h
Normal file
@ -0,0 +1,35 @@
|
||||
// Copyright 2017 The Crashpad Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef CRASHPAD_COMPAT_ANDROID_DLFCN_INTERNAL_H_
|
||||
#define CRASHPAD_COMPAT_ANDROID_DLFCN_INTERNAL_H_
|
||||
|
||||
namespace crashpad {
|
||||
namespace internal {
|
||||
|
||||
//! \brief Provide a wrapper for `dlsym`.
|
||||
//!
|
||||
//! dlsym on Android KitKat (4.4.*) raises SIGFPE when searching for a
|
||||
//! non-existent symbol. This wrapper avoids crashing in this circumstance.
|
||||
//! https://code.google.com/p/android/issues/detail?id=61799
|
||||
//!
|
||||
//! The parameters and return value for this function are the same as for
|
||||
//! `dlsym`, but a return value for `dlerror` may not be set in the event of an
|
||||
//! error.
|
||||
void* Dlsym(void* handle, const char* symbol);
|
||||
|
||||
} // namespace internal
|
||||
} // namespace crashpad
|
||||
|
||||
#endif // CRASHPAD_COMPAT_ANDROID_DLFCN_INTERNAL_H_
|
@ -17,6 +17,8 @@
|
||||
|
||||
#include_next <elf.h>
|
||||
|
||||
#include <android/api-level.h>
|
||||
|
||||
#if !defined(ELF32_ST_VISIBILITY)
|
||||
#define ELF32_ST_VISIBILITY(other) ((other) & 0x3)
|
||||
#endif
|
||||
@ -35,4 +37,22 @@
|
||||
#define STT_TLS 6
|
||||
#endif
|
||||
|
||||
// ELF note header types are normally provided by <linux/elf.h>. While unified
|
||||
// headers include <linux/elf.h> in <elf.h>, traditional headers do not, prior
|
||||
// to API 21. <elf.h> and <linux/elf.h> can't both be included in the same
|
||||
// translation unit due to collisions, so we provide these types here.
|
||||
#if __ANDROID_API__ < 21 && !defined(__ANDROID_API_N__)
|
||||
typedef struct {
|
||||
Elf32_Word n_namesz;
|
||||
Elf32_Word n_descsz;
|
||||
Elf32_Word n_type;
|
||||
} Elf32_Nhdr;
|
||||
|
||||
typedef struct {
|
||||
Elf64_Word n_namesz;
|
||||
Elf64_Word n_descsz;
|
||||
Elf64_Word n_type;
|
||||
} Elf64_Nhdr;
|
||||
#endif // __ANDROID_API__ < 21 && !defined(NT_PRSTATUS)
|
||||
|
||||
#endif // CRASHPAD_COMPAT_ANDROID_ELF_H_
|
||||
|
36
compat/android/sys/epoll.cc
Normal file
36
compat/android/sys/epoll.cc
Normal file
@ -0,0 +1,36 @@
|
||||
// Copyright 2017 The Crashpad Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <sys/epoll.h>
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "dlfcn_internal.h"
|
||||
|
||||
#if __ANDROID_API__ < 21
|
||||
|
||||
extern "C" {
|
||||
|
||||
int epoll_create1(int flags) {
|
||||
static const auto epoll_create1_p = reinterpret_cast<int (*)(int)>(
|
||||
crashpad::internal::Dlsym(RTLD_DEFAULT, "epoll_create1"));
|
||||
return epoll_create1_p ? epoll_create1_p(flags)
|
||||
: syscall(SYS_epoll_create1, flags);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
#endif // __ANDROID_API__ < 21
|
50
compat/android/sys/epoll.h
Normal file
50
compat/android/sys/epoll.h
Normal file
@ -0,0 +1,50 @@
|
||||
// Copyright 2017 The Crashpad Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef CRASHPAD_COMPAT_ANDROID_SYS_EPOLL_H_
|
||||
#define CRASHPAD_COMPAT_ANDROID_SYS_EPOLL_H_
|
||||
|
||||
#include_next <sys/epoll.h>
|
||||
|
||||
#include <android/api-level.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
// This is missing from traditional headers before API 21.
|
||||
#if !defined(EPOLLRDHUP)
|
||||
#define EPOLLRDHUP 0x00002000
|
||||
#endif
|
||||
|
||||
// EPOLL_CLOEXEC is undefined in traditional headers before API 21 and removed
|
||||
// from unified headers at API levels < 21 as a means to indicate that
|
||||
// epoll_create1 is missing from the C library, but the raw system call should
|
||||
// still be available.
|
||||
#if !defined(EPOLL_CLOEXEC)
|
||||
#define EPOLL_CLOEXEC O_CLOEXEC
|
||||
#endif
|
||||
|
||||
#if __ANDROID_API__ < 21
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
int epoll_create1(int flags);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif // __ANDROID_API__ < 21
|
||||
|
||||
#endif // CRASHPAD_COMPAT_ANDROID_SYS_EPOLL_H_
|
@ -19,6 +19,8 @@
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "dlfcn_internal.h"
|
||||
|
||||
#if defined(__USE_FILE_OFFSET64) && __ANDROID_API__ < 21
|
||||
|
||||
// Bionic has provided a wrapper for __mmap2() since the beginning of time. See
|
||||
@ -87,8 +89,8 @@ void* mmap(void* addr, size_t size, int prot, int flags, int fd, off_t offset) {
|
||||
// Use the system’s mmap64() wrapper if available. It will be available on
|
||||
// Android 5.0 (“Lollipop”) and later.
|
||||
using Mmap64Type = void* (*)(void*, size_t, int, int, int, off64_t);
|
||||
static const Mmap64Type mmap64 =
|
||||
reinterpret_cast<Mmap64Type>(dlsym(RTLD_DEFAULT, "mmap64"));
|
||||
static const Mmap64Type mmap64 = reinterpret_cast<Mmap64Type>(
|
||||
crashpad::internal::Dlsym(RTLD_DEFAULT, "mmap64"));
|
||||
if (mmap64) {
|
||||
return mmap64(addr, size, prot, flags, fd, offset);
|
||||
}
|
||||
|
@ -19,6 +19,10 @@
|
||||
|
||||
// Android 5.0.0 (API 21) NDK
|
||||
|
||||
#if !defined(SYS_epoll_create1)
|
||||
#define SYS_epoll_create1 __NR_epoll_create1
|
||||
#endif
|
||||
|
||||
#if !defined(SYS_gettid)
|
||||
#define SYS_gettid __NR_gettid
|
||||
#endif
|
||||
|
@ -19,25 +19,27 @@
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'crashpad_compat',
|
||||
'type': 'static_library',
|
||||
'sources': [
|
||||
'android/dlfcn_internal.cc',
|
||||
'android/dlfcn_internal.h',
|
||||
'android/elf.h',
|
||||
'android/linux/elf.h',
|
||||
'android/linux/prctl.h',
|
||||
'android/linux/ptrace.h',
|
||||
'android/sched.h',
|
||||
'android/sys/epoll.cc',
|
||||
'android/sys/epoll.h',
|
||||
'android/sys/mman.cc',
|
||||
'android/sys/mman.h',
|
||||
'android/sys/syscall.h',
|
||||
'android/sys/user.h',
|
||||
'linux/signal.h',
|
||||
'linux/sys/ptrace.h',
|
||||
'linux/sys/user.h',
|
||||
'mac/AvailabilityMacros.h',
|
||||
'mac/kern/exc_resource.h',
|
||||
'mac/mach/i386/thread_state.h',
|
||||
'mac/mach/mach.h',
|
||||
'mac/mach-o/getsect.cc',
|
||||
'mac/mach-o/getsect.h',
|
||||
'mac/mach-o/loader.h',
|
||||
'mac/sys/resource.h',
|
||||
'non_mac/mach/mach.h',
|
||||
@ -60,9 +62,7 @@
|
||||
],
|
||||
'conditions': [
|
||||
['OS=="mac"', {
|
||||
'dependencies': [
|
||||
'../third_party/apple_cctools/apple_cctools.gyp:apple_cctools',
|
||||
],
|
||||
'type': 'none',
|
||||
'include_dirs': [
|
||||
'mac',
|
||||
],
|
||||
@ -71,8 +71,18 @@
|
||||
'mac',
|
||||
],
|
||||
},
|
||||
}, {
|
||||
'include_dirs': [
|
||||
'non_mac',
|
||||
],
|
||||
'direct_dependent_settings': {
|
||||
'include_dirs': [
|
||||
'non_mac',
|
||||
],
|
||||
},
|
||||
}],
|
||||
['OS=="win"', {
|
||||
'type': 'static_library',
|
||||
'include_dirs': [
|
||||
'win',
|
||||
],
|
||||
@ -95,6 +105,7 @@
|
||||
},
|
||||
}],
|
||||
['OS=="android"', {
|
||||
'type': 'static_library',
|
||||
'include_dirs': [
|
||||
'android',
|
||||
'linux',
|
||||
@ -112,6 +123,7 @@
|
||||
},
|
||||
}],
|
||||
['OS=="linux"', {
|
||||
'type': 'none',
|
||||
'include_dirs': [
|
||||
'linux',
|
||||
],
|
||||
|
@ -18,10 +18,43 @@
|
||||
#include_next <signal.h>
|
||||
|
||||
// Missing from glibc and bionic-x86_64
|
||||
|
||||
#if defined(__x86_64__) || defined(__i386__)
|
||||
#if !defined(X86_FXSR_MAGIC)
|
||||
#define X86_FXSR_MAGIC 0x0000
|
||||
#endif
|
||||
#endif // __x86_64__ || __i386__
|
||||
|
||||
#if defined(__aarch64__) || defined(__arm__)
|
||||
|
||||
#if !defined(FPSIMD_MAGIC)
|
||||
#define FPSIMD_MAGIC 0x46508001
|
||||
#endif
|
||||
|
||||
#if !defined(ESR_MAGIC)
|
||||
#define ESR_MAGIC 0x45535201
|
||||
#endif
|
||||
|
||||
#if !defined(EXTRA_MAGIC)
|
||||
#define EXTRA_MAGIC 0x45585401
|
||||
#endif
|
||||
|
||||
#if !defined(VFP_MAGIC)
|
||||
#define VFP_MAGIC 0x56465001
|
||||
#endif
|
||||
|
||||
#if !defined(CRUNCH_MAGIC)
|
||||
#define CRUNCH_MAGIC 0x5065cf03
|
||||
#endif
|
||||
|
||||
#if !defined(DUMMY_MAGIC)
|
||||
#define DUMMY_MAGIC 0xb0d9ed01
|
||||
#endif
|
||||
|
||||
#if !defined(IWMMXT_MAGIC)
|
||||
#define IWMMXT_MAGIC 0x12ef842a
|
||||
#endif
|
||||
|
||||
#endif // __aarch64__ || __arm__
|
||||
|
||||
#endif // CRASHPAD_COMPAT_LINUX_SIGNAL_H_
|
||||
|
@ -19,9 +19,33 @@
|
||||
|
||||
#include <sys/cdefs.h>
|
||||
|
||||
#if defined(__GLIBC__) && defined(__x86_64__)
|
||||
// https://sourceware.org/bugzilla/show_bug.cgi?id=22433
|
||||
#if !defined(PTRACE_GET_THREAD_AREA) && !defined(PT_GET_THREAD_AREA) && \
|
||||
defined(__GLIBC__)
|
||||
#if defined(__i386__) || defined(__x86_64__)
|
||||
static constexpr __ptrace_request PTRACE_GET_THREAD_AREA =
|
||||
static_cast<__ptrace_request>(25);
|
||||
#endif // __GLIBC__ && __x86_64__
|
||||
#define PTRACE_GET_THREAD_AREA PTRACE_GET_THREAD_AREA
|
||||
#elif defined(__arm__) || defined(__arm64__)
|
||||
static constexpr __ptrace_request PTRACE_GET_THREAD_AREA =
|
||||
static_cast<__ptrace_request>(22);
|
||||
#define PTRACE_GET_THREAD_AREA PTRACE_GET_THREAD_AREA
|
||||
#elif defined(__mips__)
|
||||
static constexpr __ptrace_request PTRACE_GET_THREAD_AREA =
|
||||
static_cast<__ptrace_request>(25);
|
||||
#define PTRACE_GET_THREAD_AREA PTRACE_GET_THREAD_AREA
|
||||
static constexpr __ptrace_request PTRACE_GET_THREAD_AREA_3264 =
|
||||
static_cast<__ptrace_request>(0xc4);
|
||||
#define PTRACE_GET_THREAD_AREA_3264 PTRACE_GET_THREAD_AREA_3264
|
||||
#endif
|
||||
#endif // !PTRACE_GET_THREAD_AREA && !PT_GET_THREAD_AREA && defined(__GLIBC__)
|
||||
|
||||
// https://sourceware.org/bugzilla/show_bug.cgi?id=22433
|
||||
#if !defined(PTRACE_GETVFPREGS) && !defined(PT_GETVFPREGS) && \
|
||||
defined(__GLIBC__) && (defined(__arm__) || defined(__arm64__))
|
||||
static constexpr __ptrace_request PTRACE_GETVFPREGS =
|
||||
static_cast<__ptrace_request>(27);
|
||||
#define PTRACE_GETVFPREGS PTRACE_GETVFPREGS
|
||||
#endif
|
||||
|
||||
#endif // CRASHPAD_COMPAT_LINUX_SYS_PTRACE_H_
|
||||
|
30
compat/linux/sys/user.h
Normal file
30
compat/linux/sys/user.h
Normal file
@ -0,0 +1,30 @@
|
||||
// Copyright 2018 The Crashpad Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef CRASHPAD_COMPAT_LINUX_SYS_USER_H_
|
||||
#define CRASHPAD_COMPAT_LINUX_SYS_USER_H_
|
||||
|
||||
#include_next <sys/user.h>
|
||||
|
||||
#include <features.h>
|
||||
|
||||
// glibc for 64-bit ARM uses different names for these structs prior to 2.20.
|
||||
#if defined(__arm64__) && defined(__GLIBC__)
|
||||
#if !__GLIBC_PREREQ(2, 20)
|
||||
using user_regs_struct = user_pt_regs;
|
||||
using user_fpsimd_struct = user_fpsimd_state;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif // CRASHPAD_COMPAT_LINUX_SYS_USER_H_
|
@ -1,107 +0,0 @@
|
||||
// Copyright 2014 The Crashpad Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <mach-o/getsect.h>
|
||||
|
||||
// This is only necessary when building code that might run on systems earlier
|
||||
// than 10.7. When building for 10.7 or later, getsectiondata() and
|
||||
// getsegmentdata() are always present in libmacho and made available through
|
||||
// libSystem. When building for earlier systems, custom definitions of
|
||||
// these functions are needed.
|
||||
//
|
||||
// This file checks the deployment target instead of the SDK. The deployment
|
||||
// target is correct because it identifies the earliest possible system that
|
||||
// the code being compiled is expected to run on.
|
||||
|
||||
#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_7
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "third_party/apple_cctools/cctools/include/mach-o/getsect.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// Returns a dlopen() handle to the same library that provides the
|
||||
// getsectbyname() function. getsectbyname() is always present in libmacho.
|
||||
// getsectiondata() and getsegmentdata() are not always present, but when they
|
||||
// are, they’re in the same library as getsectbyname(). If the library cannot
|
||||
// be found or a handle to it cannot be returned, returns nullptr.
|
||||
void* SystemLibMachOHandle() {
|
||||
Dl_info info;
|
||||
if (!dladdr(reinterpret_cast<void*>(getsectbyname), &info)) {
|
||||
return nullptr;
|
||||
}
|
||||
return dlopen(info.dli_fname, RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD);
|
||||
}
|
||||
|
||||
// Returns a function pointer to a function in libmacho based on a lookup of
|
||||
// that function by symbol name. Returns nullptr if libmacho cannot be found or
|
||||
// opened, or if the named symbol cannot be found in libmacho.
|
||||
void* LookUpSystemLibMachOSymbol(const char* symbol) {
|
||||
static void* dl_handle = SystemLibMachOHandle();
|
||||
if (!dl_handle) {
|
||||
return nullptr;
|
||||
}
|
||||
return dlsym(dl_handle, symbol);
|
||||
}
|
||||
|
||||
#ifndef __LP64__
|
||||
using MachHeader = mach_header;
|
||||
#else
|
||||
using MachHeader = mach_header_64;
|
||||
#endif
|
||||
|
||||
using GetSectionDataType =
|
||||
uint8_t*(*)(const MachHeader*, const char*, const char*, unsigned long*);
|
||||
using GetSegmentDataType =
|
||||
uint8_t*(*)(const MachHeader*, const char*, unsigned long*);
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" {
|
||||
|
||||
// These implementations look up their functions in libmacho at run time. If the
|
||||
// system libmacho provides these functions as it normally does on OS X 10.7 and
|
||||
// later, the system’s versions are used directly. Otherwise, the versions in
|
||||
// third_party/apple_cctools are used, which are actually just copies of the
|
||||
// system’s functions.
|
||||
|
||||
uint8_t* getsectiondata(const MachHeader* mhp,
|
||||
const char* segname,
|
||||
const char* sectname,
|
||||
unsigned long* size) {
|
||||
static GetSectionDataType system_getsectiondata =
|
||||
reinterpret_cast<GetSectionDataType>(
|
||||
LookUpSystemLibMachOSymbol("getsectiondata"));
|
||||
if (system_getsectiondata) {
|
||||
return system_getsectiondata(mhp, segname, sectname, size);
|
||||
}
|
||||
return crashpad_getsectiondata(mhp, segname, sectname, size);
|
||||
}
|
||||
|
||||
uint8_t* getsegmentdata(
|
||||
const MachHeader* mhp, const char* segname, unsigned long* size) {
|
||||
static GetSegmentDataType system_getsegmentdata =
|
||||
reinterpret_cast<GetSegmentDataType>(
|
||||
LookUpSystemLibMachOSymbol("getsegmentdata"));
|
||||
if (system_getsegmentdata) {
|
||||
return system_getsegmentdata(mhp, segname, size);
|
||||
}
|
||||
return crashpad_getsegmentdata(mhp, segname, size);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
#endif // MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_7
|
@ -1,69 +0,0 @@
|
||||
// Copyright 2014 The Crashpad Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef CRASHPAD_COMPAT_MAC_MACH_O_GETSECT_H_
|
||||
#define CRASHPAD_COMPAT_MAC_MACH_O_GETSECT_H_
|
||||
|
||||
#include_next <mach-o/getsect.h>
|
||||
|
||||
#include <AvailabilityMacros.h>
|
||||
|
||||
// This file checks the SDK instead of the deployment target. The SDK is correct
|
||||
// because this file is concerned with providing compile-time declarations,
|
||||
// which are either present in a specific SDK version or not.
|
||||
|
||||
#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
|
||||
|
||||
#include <mach-o/loader.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Don’t use a type alias to account for the mach_header/mach_header_64
|
||||
// difference between the 32-bit and 64-bit versions of getsectiondata() and
|
||||
// getsegmentdata(). This file should be faithfully equivalent to the native
|
||||
// SDK, and adding type aliases here would pollute the namespace in a way that
|
||||
// the native SDK does not.
|
||||
|
||||
#if !defined(__LP64__)
|
||||
|
||||
uint8_t* getsectiondata(const struct mach_header* mhp,
|
||||
const char* segname,
|
||||
const char* sectname,
|
||||
unsigned long* size);
|
||||
|
||||
uint8_t* getsegmentdata(
|
||||
const struct mach_header* mhp, const char* segname, unsigned long* size);
|
||||
|
||||
#else
|
||||
|
||||
uint8_t* getsectiondata(const struct mach_header_64* mhp,
|
||||
const char* segname,
|
||||
const char* sectname,
|
||||
unsigned long* size);
|
||||
|
||||
uint8_t* getsegmentdata(
|
||||
const struct mach_header_64* mhp, const char* segname, unsigned long* size);
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif // MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
|
||||
|
||||
#endif // CRASHPAD_COMPAT_MAC_MACH_O_GETSECT_H_
|
@ -564,16 +564,15 @@ struct __attribute__((packed, aligned(4))) MINIDUMP_MODULE {
|
||||
//! <a
|
||||
//! href="http://pierrelib.pagesperso-orange.fr/exec_formats/MS_Symbol_Type_v1.0.pdf#page=71">Microsoft
|
||||
//! Symbol and Type Information</a>, section 7.2, “Debug Information Format”
|
||||
//! for a list of debug information formats, and <a
|
||||
//! href="http://undocumented.rawol.com/sbs-w2k-1-windows-2000-debugging-support.pdf#page=63">Undocumented
|
||||
//! Windows 2000 Secrets</a>, Windows 2000 Debugging Support/Microsoft Symbol
|
||||
//! File Internals/CodeView Subsections for an in-depth description of the
|
||||
//! CodeView 4.1 format. Signatures seen in the wild include “NB09”
|
||||
//! (0x3930424e) for CodeView 4.1 and “NB11” (0x3131424e) for CodeView 5.0.
|
||||
//! This form of debugging information within the module, as opposed to a link
|
||||
//! to an external `.pdb` file, is chosen by building with `/Z7` in Visual
|
||||
//! Studio 6.0 (1998) and earlier. This embedded form of debugging information
|
||||
//! is now considered obsolete.
|
||||
//! for a list of debug information formats, and <i>Undocumented Windows 2000
|
||||
//! Secrets</i>, Windows 2000 Debugging Support/Microsoft Symbol File
|
||||
//! Internals/CodeView Subsections for an in-depth description of the CodeView
|
||||
//! 4.1 format. Signatures seen in the wild include “NB09” (0x3930424e) for
|
||||
//! CodeView 4.1 and “NB11” (0x3131424e) for CodeView 5.0. This form of
|
||||
//! debugging information within the module, as opposed to a link to an
|
||||
//! external `.pdb` file, is chosen by building with `/Z7` in Visual Studio
|
||||
//! 6.0 (1998) and earlier. This embedded form of debugging information is now
|
||||
//! considered obsolete.
|
||||
//!
|
||||
//! On Windows, the CodeView record is taken from a module’s
|
||||
//! IMAGE_DEBUG_DIRECTORY entry whose Type field has the value
|
||||
|
@ -18,8 +18,8 @@
|
||||
// include_next <winnt.h>
|
||||
#include <../um/winnt.h>
|
||||
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa373184.aspx:
|
||||
// "Note that this structure definition was accidentally omitted from WinNT.h."
|
||||
// https://msdn.microsoft.com/library/aa373184.aspx: "Note that this structure
|
||||
// definition was accidentally omitted from WinNT.h."
|
||||
struct PROCESSOR_POWER_INFORMATION {
|
||||
ULONG Number;
|
||||
ULONG MaxMhz;
|
||||
|
@ -22,7 +22,7 @@ limitations under the License.
|
||||
|
||||
## Introduction
|
||||
|
||||
Crashpad is a [Chromium project](https://dev.chromium.org/Home). Most of its
|
||||
Crashpad is a [Chromium project](https://www.chromium.org/Home). Most of its
|
||||
development practices follow Chromium’s. In order to function on its own in
|
||||
other projects, Crashpad uses
|
||||
[mini_chromium](https://chromium.googlesource.com/chromium/mini_chromium/), a
|
||||
@ -43,9 +43,9 @@ the `$PATH` environment variable:
|
||||
C++ support and the Windows SDK. MSVS 2015 and MSVS 2017 are both
|
||||
supported. Some tests also require the CDB debugger, installed with
|
||||
[Debugging Tools for
|
||||
Windows](https://msdn.microsoft.com/library/windows/hardware/ff551063.aspx).
|
||||
Windows](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/).
|
||||
* Chromium’s
|
||||
[depot_tools](https://dev.chromium.org/developers/how-tos/depottools).
|
||||
[depot_tools](https://www.chromium.org/developers/how-tos/depottools).
|
||||
* [Git](https://git-scm.com/). This is provided by Xcode on macOS and by
|
||||
depot_tools on Windows.
|
||||
* [Python](https://www.python.org/). This is provided by the operating system
|
||||
@ -57,12 +57,12 @@ The main source code repository is a Git repository hosted at
|
||||
https://chromium.googlesource.com/crashpad/crashpad. Although it is possible to
|
||||
check out this repository directly with `git clone`, Crashpad’s dependencies are
|
||||
managed by
|
||||
[`gclient`](https://dev.chromium.org/developers/how-tos/depottools#TOC-gclient)
|
||||
[`gclient`](https://www.chromium.org/developers/how-tos/depottools#TOC-gclient)
|
||||
instead of Git submodules, so to work on Crashpad, it is best to use `fetch` to
|
||||
get the source code.
|
||||
|
||||
`fetch` and `gclient` are part of the
|
||||
[depot_tools](https://dev.chromium.org/developers/how-tos/depottools). There’s
|
||||
[depot_tools](https://www.chromium.org/developers/how-tos/depottools). There’s
|
||||
no need to install them separately.
|
||||
|
||||
### Initial Checkout
|
||||
@ -86,28 +86,25 @@ $ gclient sync
|
||||
|
||||
## Building
|
||||
|
||||
Crashpad uses [GYP](https://gyp.gsrc.io/) to generate
|
||||
[Ninja](https://ninja-build.org/) build files. The build is described by `.gyp`
|
||||
files throughout the Crashpad source code tree. The
|
||||
[`build/gyp_crashpad.py`](https://chromium.googlesource.com/crashpad/crashpad/+/master/build/gyp_crashpad.py)
|
||||
script runs GYP properly for Crashpad, and is also called when you run `fetch
|
||||
crashpad`, `gclient sync`, or `gclient runhooks`.
|
||||
### Windows, Mac, Linux, Fuchsia
|
||||
|
||||
The Ninja build files and build output are in the `out` directory. Both debug-
|
||||
and release-mode configurations are available. The examples below show the debug
|
||||
configuration. To build and test the release configuration, substitute `Release`
|
||||
for `Debug`. On Windows, four configurations are available: `Debug` and
|
||||
`Release` produce 32-bit x86 executables, and `Debug_x64` and `Release_x64`
|
||||
produce x86_64 executables.
|
||||
On Windows, Mac, Linux, and Fuchsia Crashpad uses
|
||||
[GN](https://gn.googlesource.com/gn) to generate
|
||||
[Ninja](https://ninja-build.org/) build files. For example,
|
||||
|
||||
```
|
||||
$ cd ~/crashpad/crashpad
|
||||
$ ninja -C out/Debug
|
||||
$ gn gen out/Default
|
||||
$ ninja -C out/Default
|
||||
```
|
||||
|
||||
Ninja is part of the
|
||||
[depot_tools](https://dev.chromium.org/developers/how-tos/depottools). There’s
|
||||
no need to install it separately.
|
||||
You can then use `gn args out/Default` or edit `out/Default/args.gn` to
|
||||
configure the build, for example things like `is_debug=true` or
|
||||
`target_cpu="x86"`.
|
||||
|
||||
GN and Ninja are part of the
|
||||
[depot_tools](https://www.chromium.org/developers/how-tos/depottools). There’s
|
||||
no need to install them separately.
|
||||
|
||||
### Android
|
||||
|
||||
@ -119,7 +116,7 @@ Kit)](https://developer.android.com/ndk/) runs on.
|
||||
If it’s not already present on your system, [download the NDK package for your
|
||||
system](https://developer.android.com/ndk/downloads/) and expand it to a
|
||||
suitable location. These instructions assume that it’s been expanded to
|
||||
`~/android-ndk-r15b`.
|
||||
`~/android-ndk-r16`.
|
||||
|
||||
To build Crashpad, portions of the NDK must be reassembled into a [standalone
|
||||
toolchain](https://developer.android.com/ndk/guides/standalone_toolchain.html).
|
||||
@ -133,8 +130,8 @@ desired. To build a standalone toolchain targeting 64-bit ARM and API level 21
|
||||
|
||||
```
|
||||
$ cd ~
|
||||
$ python android-ndk-r15b/build/tools/make_standalone_toolchain.py \
|
||||
--arch=arm64 --api=21 --install-dir=android-ndk-r15b_arm64_api21
|
||||
$ python android-ndk-r16/build/tools/make_standalone_toolchain.py \
|
||||
--arch=arm64 --api=21 --install-dir=android-ndk-r16_arm64_api21
|
||||
```
|
||||
|
||||
Note that Chrome uses Android API level 21 for 64-bit platforms and 16 for
|
||||
@ -152,7 +149,7 @@ operation.
|
||||
```
|
||||
$ cd ~/crashpad/crashpad
|
||||
$ python build/gyp_crashpad_android.py \
|
||||
--ndk ~/android-ndk-r15b_arm64_api21 \
|
||||
--ndk ~/android-ndk-r16_arm64_api21 \
|
||||
--generator-output out/android_arm64_api21
|
||||
```
|
||||
|
||||
@ -200,7 +197,7 @@ $ python build/run_tests.py out/Debug
|
||||
|
||||
On Windows, `end_to_end_test.py` requires the CDB debugger, installed with
|
||||
[Debugging Tools for
|
||||
Windows](https://msdn.microsoft.com/library/windows/hardware/ff551063.aspx).
|
||||
Windows](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/).
|
||||
This can be installed either as part of the [Windows Driver
|
||||
Kit](https://go.microsoft.com/fwlink/p?LinkID=239721) or the [Windows
|
||||
SDK](https://go.microsoft.com/fwlink/p?LinkID=271979). If the Windows SDK has
|
||||
@ -210,40 +207,23 @@ Software Development Kit.
|
||||
|
||||
### Android
|
||||
|
||||
To test on Android, use [ADB (Android Debug
|
||||
Bridge)](https://developer.android.com/studio/command-line/adb.html) to `adb
|
||||
push` test executables and test data to a device or emulator, then use `adb
|
||||
shell` to get a shell to run the test executables from. ADB is part of the
|
||||
[Android SDK](https://developer.android.com/sdk/). Note that it is sufficient to
|
||||
install just the command-line tools. The entire Android Studio IDE is not
|
||||
necessary to obtain ADB.
|
||||
To test on Android, [ADB (Android Debug
|
||||
Bridge)](https://developer.android.com/studio/command-line/adb.html) from the
|
||||
[Android SDK](https://developer.android.com/sdk/) must be in the `PATH`. Note
|
||||
that it is sufficient to install just the command-line tools from the Android
|
||||
SDK. The entire Android Studio IDE is not necessary to obtain ADB.
|
||||
|
||||
This example runs `crashpad_test_test` on a device. This test executable has a
|
||||
run-time dependency on a second executable and a test data file, which are also
|
||||
transferred to the device prior to running the test.
|
||||
|
||||
```
|
||||
$ cd ~/crashpad/crashpad
|
||||
$ adb push out/android_arm64_api21/out/Debug/crashpad_test_test /data/local/tmp/
|
||||
[100%] /data/local/tmp/crashpad_test_test
|
||||
$ adb push \
|
||||
out/android_arm64_api21/out/Debug/crashpad_test_test_multiprocess_exec_test_child \
|
||||
/data/local/tmp/
|
||||
[100%] /data/local/tmp/crashpad_test_test_multiprocess_exec_test_child
|
||||
$ adb shell mkdir -p /data/local/tmp/crashpad_test_data_root/test
|
||||
$ adb push test/test_paths_test_data_root.txt \
|
||||
/data/local/tmp/crashpad_test_data_root/test/
|
||||
[100%] /data/local/tmp/crashpad_test_data_root/test/test_paths_test_data_root.txt
|
||||
$ adb shell
|
||||
device:/ $ cd /data/local/tmp
|
||||
device:/data/local/tmp $ CRASHPAD_TEST_DATA_ROOT=crashpad_test_data_root \
|
||||
./crashpad_test_test
|
||||
```
|
||||
When asked to test an Android build directory, `run_tests.py` will detect a
|
||||
single connected Android device (including an emulator). If multiple devices are
|
||||
connected, one may be chosen explicitly with the `ANDROID_DEVICE` environment
|
||||
variable. `run_tests.py` will upload test executables and data to a temporary
|
||||
location on the detected or selected device, run them, and clean up after itself
|
||||
when done.
|
||||
|
||||
## Contributing
|
||||
|
||||
Crashpad’s contribution process is very similar to [Chromium’s contribution
|
||||
process](https://dev.chromium.org/developers/contributing-code).
|
||||
process](https://www.chromium.org/developers/contributing-code).
|
||||
|
||||
### Code Review
|
||||
|
||||
@ -256,7 +236,7 @@ must be sent to an appropriate reviewer, with a Cc sent to
|
||||
file specifies this environment to `git-cl`.
|
||||
|
||||
`git-cl` is part of the
|
||||
[depot_tools](https://dev.chromium.org/developers/how-tos/depottools). There’s
|
||||
[depot_tools](https://www.chromium.org/developers/how-tos/depottools). There’s
|
||||
no need to install it separately.
|
||||
|
||||
```
|
||||
@ -282,7 +262,7 @@ patch set with `git cl upload` and let your reviewer know you’ve addressed the
|
||||
feedback.
|
||||
|
||||
The most recently uploaded patch set on a review may be tested on a [try
|
||||
server](https://dev.chromium.org/developers/testing/try-server-usage) by running
|
||||
server](https://www.chromium.org/developers/testing/try-server-usage) by running
|
||||
`git cl try` or by clicking the “CQ Dry Run” button in Gerrit. These set the
|
||||
“Commit-Queue: +1” label. This does not mean that the patch will be committed,
|
||||
but the try server and commit queue share infrastructure and a Gerrit label. The
|
||||
@ -294,7 +274,7 @@ Crashpad and Chromium committers.
|
||||
|
||||
After code review is complete and “Code-Review: +1” has been received from all
|
||||
reviewers, the patch can be submitted to Crashpad’s [commit
|
||||
queue](https://dev.chromium.org/developers/testing/commit-queue) by clicking the
|
||||
queue](https://www.chromium.org/developers/testing/commit-queue) by clicking the
|
||||
“Submit to CQ” button in Gerrit. This sets the “Commit-Queue: +2” label, which
|
||||
tests the patch on the try server before landing it. Commit queue access is
|
||||
available to Crashpad and Chromium committers.
|
||||
|
@ -21,7 +21,7 @@ limitations under the License.
|
||||
Crashpad currently consists of a crash-reporting client and some related tools
|
||||
for macOS and Windows. The core client work for both platforms is substantially
|
||||
complete. Crashpad became the crash reporter client for
|
||||
[Chromium](https://dev.chromium.org/Home) on macOS as of [March
|
||||
[Chromium](https://www.chromium.org/Home) on macOS as of [March
|
||||
2015](https://chromium.googlesource.com/chromium/src/\+/d413b2dcb54d523811d386f1ff4084f677a6d089),
|
||||
and on Windows as of [November
|
||||
2015](https://chromium.googlesource.com/chromium/src/\+/cfa5b01bb1d06bf96967bd37e21a44752801948c).
|
||||
|
@ -37,7 +37,7 @@ def main(args):
|
||||
elif os.path.exists(output_dir):
|
||||
os.unlink(output_dir)
|
||||
|
||||
os.makedirs(output_dir, 0755)
|
||||
os.makedirs(output_dir, 0o755)
|
||||
|
||||
doxy_file = os.path.join('doc', 'support', 'crashpad.doxy')
|
||||
subprocess.check_call(['doxygen', doxy_file])
|
||||
|
334
handler/BUILD.gn
Normal file
334
handler/BUILD.gn
Normal file
@ -0,0 +1,334 @@
|
||||
# Copyright 2015 The Crashpad Authors. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import("../build/crashpad_buildconfig.gni")
|
||||
|
||||
static_library("handler") {
|
||||
sources = [
|
||||
"crash_report_upload_thread.cc",
|
||||
"crash_report_upload_thread.h",
|
||||
"handler_main.cc",
|
||||
"handler_main.h",
|
||||
"minidump_to_upload_parameters.cc",
|
||||
"minidump_to_upload_parameters.h",
|
||||
"prune_crash_reports_thread.cc",
|
||||
"prune_crash_reports_thread.h",
|
||||
"user_stream_data_source.cc",
|
||||
"user_stream_data_source.h",
|
||||
]
|
||||
|
||||
if (crashpad_is_mac) {
|
||||
sources += [
|
||||
"mac/crash_report_exception_handler.cc",
|
||||
"mac/crash_report_exception_handler.h",
|
||||
"mac/exception_handler_server.cc",
|
||||
"mac/exception_handler_server.h",
|
||||
"mac/file_limit_annotation.cc",
|
||||
"mac/file_limit_annotation.h",
|
||||
]
|
||||
}
|
||||
|
||||
if (crashpad_is_linux || crashpad_is_android) {
|
||||
set_sources_assignment_filter([])
|
||||
sources += [
|
||||
"linux/crash_report_exception_handler.cc",
|
||||
"linux/crash_report_exception_handler.h",
|
||||
"linux/exception_handler_server.cc",
|
||||
"linux/exception_handler_server.h",
|
||||
]
|
||||
}
|
||||
|
||||
if (crashpad_is_win) {
|
||||
sources += [
|
||||
"win/crash_report_exception_handler.cc",
|
||||
"win/crash_report_exception_handler.h",
|
||||
]
|
||||
}
|
||||
|
||||
if (crashpad_is_fuchsia) {
|
||||
sources += [
|
||||
"fuchsia/crash_report_exception_handler.cc",
|
||||
"fuchsia/crash_report_exception_handler.h",
|
||||
"fuchsia/exception_handler_server.cc",
|
||||
"fuchsia/exception_handler_server.h",
|
||||
]
|
||||
}
|
||||
|
||||
public_configs = [ "..:crashpad_config" ]
|
||||
|
||||
deps = [
|
||||
"../client",
|
||||
"../compat",
|
||||
"../minidump",
|
||||
"../snapshot",
|
||||
"../third_party/mini_chromium:base",
|
||||
"../tools:tool_support",
|
||||
"../util",
|
||||
]
|
||||
|
||||
if (crashpad_is_win) {
|
||||
cflags = [ "/wd4201" ] # nonstandard extension used : nameless struct/union
|
||||
}
|
||||
}
|
||||
|
||||
source_set("handler_test") {
|
||||
testonly = true
|
||||
|
||||
sources = [
|
||||
"minidump_to_upload_parameters_test.cc",
|
||||
]
|
||||
|
||||
if (crashpad_is_linux || crashpad_is_android) {
|
||||
sources += [ "linux/exception_handler_server_test.cc" ]
|
||||
}
|
||||
|
||||
if (crashpad_is_win) {
|
||||
sources += [ "crashpad_handler_test.cc" ]
|
||||
}
|
||||
|
||||
deps = [
|
||||
":handler",
|
||||
"../client",
|
||||
"../compat",
|
||||
"../snapshot",
|
||||
"../snapshot:test_support",
|
||||
"../test",
|
||||
"../third_party/gtest:gtest",
|
||||
"../third_party/mini_chromium:base",
|
||||
"../util",
|
||||
]
|
||||
|
||||
if (crashpad_is_win) {
|
||||
data_deps = [
|
||||
":crashpad_handler_test_extended_handler",
|
||||
":fake_handler_that_crashes_at_startup",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
crashpad_executable("crashpad_handler") {
|
||||
sources = [
|
||||
"main.cc",
|
||||
]
|
||||
|
||||
deps = [
|
||||
":handler",
|
||||
"../build:default_exe_manifest_win",
|
||||
"../compat",
|
||||
"../third_party/mini_chromium:base",
|
||||
]
|
||||
|
||||
if (crashpad_is_mac && crashpad_is_in_chromium) {
|
||||
if (is_component_build) {
|
||||
ldflags = [
|
||||
# The handler is in
|
||||
# Chromium.app/Contents/Versions/X/Chromium Framework.framework/Versions/A/Helpers/
|
||||
# so set rpath up to the base.
|
||||
"-rpath",
|
||||
"@loader_path/../../../../../../../..",
|
||||
|
||||
# The handler is also in
|
||||
# Content Shell.app/Contents/Frameworks/Content Shell Framework.framework/Helpers/
|
||||
# so set the rpath for that too.
|
||||
"-rpath",
|
||||
"@loader_path/../../../../..",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
if (crashpad_is_win) {
|
||||
if (crashpad_is_in_chromium) {
|
||||
remove_configs = [ "//build/config/win:console" ]
|
||||
configs = [ "//build/config/win:windowed" ]
|
||||
} else {
|
||||
remove_configs =
|
||||
[ "//third_party/mini_chromium/mini_chromium/build:win_console" ]
|
||||
configs =
|
||||
[ "//third_party/mini_chromium/mini_chromium/build:win_windowed" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# There is not any normal way to package native executables in an Android APK.
|
||||
# It is normal to package native code as a loadable module but Android's APK
|
||||
# installer will ignore files not named like a shared object, so give the
|
||||
# handler executable an acceptable name.
|
||||
if (crashpad_is_android) {
|
||||
copy("crashpad_handler_module") {
|
||||
deps = [
|
||||
":crashpad_handler",
|
||||
]
|
||||
|
||||
sources = [
|
||||
"$root_out_dir/crashpad_handler",
|
||||
]
|
||||
|
||||
outputs = [
|
||||
"$root_out_dir/libcrashpad_handler.so",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
crashpad_executable("crashpad_handler_test_extended_handler") {
|
||||
testonly = true
|
||||
|
||||
sources = [
|
||||
"crashpad_handler_test_extended_handler.cc",
|
||||
]
|
||||
|
||||
deps = [
|
||||
":handler",
|
||||
"../build:default_exe_manifest_win",
|
||||
"../compat",
|
||||
"../minidump:test_support",
|
||||
"../third_party/mini_chromium:base",
|
||||
"../tools:tool_support",
|
||||
]
|
||||
}
|
||||
|
||||
if (crashpad_is_win) {
|
||||
crashpad_executable("crashpad_handler_com") {
|
||||
sources = [
|
||||
"main.cc",
|
||||
]
|
||||
|
||||
# Avoid .exp, .ilk, and .lib file collisions with crashpad_handler.exe by
|
||||
# having this target produce crashpad_handler_com.com. Don’t use this target
|
||||
# directly. Instead, use crashpad_handler_console.
|
||||
output_extension = "com"
|
||||
|
||||
deps = [
|
||||
":handler",
|
||||
"../build:default_exe_manifest_win",
|
||||
"../compat",
|
||||
"../third_party/mini_chromium:base",
|
||||
]
|
||||
}
|
||||
|
||||
copy("crashpad_handler_console") {
|
||||
deps = [
|
||||
":crashpad_handler_com",
|
||||
]
|
||||
sources = [
|
||||
"$root_out_dir/crashpad_handler_com.com",
|
||||
]
|
||||
outputs = [
|
||||
"$root_out_dir/crashpad_handler.com",
|
||||
]
|
||||
}
|
||||
|
||||
crashpad_executable("crash_other_program") {
|
||||
testonly = true
|
||||
|
||||
sources = [
|
||||
"win/crash_other_program.cc",
|
||||
]
|
||||
|
||||
deps = [
|
||||
"../client",
|
||||
"../test",
|
||||
"../third_party/gtest:gtest",
|
||||
"../third_party/mini_chromium:base",
|
||||
]
|
||||
}
|
||||
|
||||
crashpad_executable("crashy_program") {
|
||||
testonly = true
|
||||
|
||||
sources = [
|
||||
"win/crashy_test_program.cc",
|
||||
]
|
||||
|
||||
deps = [
|
||||
"../client",
|
||||
"../third_party/mini_chromium:base",
|
||||
]
|
||||
}
|
||||
|
||||
crashpad_executable("crashy_signal") {
|
||||
testonly = true
|
||||
|
||||
sources = [
|
||||
"win/crashy_signal.cc",
|
||||
]
|
||||
|
||||
cflags = [ "/wd4702" ] # Unreachable code.
|
||||
|
||||
deps = [
|
||||
"../client",
|
||||
"../third_party/mini_chromium:base",
|
||||
]
|
||||
}
|
||||
|
||||
crashpad_executable("fake_handler_that_crashes_at_startup") {
|
||||
testonly = true
|
||||
|
||||
sources = [
|
||||
"win/fake_handler_that_crashes_at_startup.cc",
|
||||
]
|
||||
}
|
||||
|
||||
crashpad_executable("hanging_program") {
|
||||
testonly = true
|
||||
|
||||
sources = [
|
||||
"win/hanging_program.cc",
|
||||
]
|
||||
|
||||
deps = [
|
||||
"../client",
|
||||
"../third_party/mini_chromium:base",
|
||||
]
|
||||
}
|
||||
|
||||
crashpad_loadable_module("loader_lock_dll") {
|
||||
testonly = true
|
||||
|
||||
sources = [
|
||||
"win/loader_lock_dll.cc",
|
||||
]
|
||||
}
|
||||
|
||||
crashpad_executable("self_destroying_program") {
|
||||
testonly = true
|
||||
|
||||
sources = [
|
||||
"win/self_destroying_test_program.cc",
|
||||
]
|
||||
|
||||
deps = [
|
||||
"../client",
|
||||
"../compat",
|
||||
"../snapshot",
|
||||
"../third_party/mini_chromium:base",
|
||||
]
|
||||
}
|
||||
|
||||
if (current_cpu == "x86") {
|
||||
# Cannot create an x64 DLL with embedded debug info.
|
||||
crashpad_executable("crashy_z7_loader") {
|
||||
testonly = true
|
||||
|
||||
sources = [
|
||||
"win/crashy_test_z7_loader.cc",
|
||||
]
|
||||
|
||||
deps = [
|
||||
"../client",
|
||||
"../test",
|
||||
"../third_party/mini_chromium:base",
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
@ -27,6 +27,7 @@
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "build/build_config.h"
|
||||
#include "client/settings.h"
|
||||
#include "handler/minidump_to_upload_parameters.h"
|
||||
#include "snapshot/minidump/process_snapshot_minidump.h"
|
||||
#include "snapshot/module_snapshot.h"
|
||||
#include "util/file/file_reader.h"
|
||||
@ -44,106 +45,6 @@
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
namespace {
|
||||
|
||||
void InsertOrReplaceMapEntry(std::map<std::string, std::string>* map,
|
||||
const std::string& key,
|
||||
const std::string& value) {
|
||||
std::string old_value;
|
||||
if (!MapInsertOrReplace(map, key, value, &old_value)) {
|
||||
LOG(WARNING) << "duplicate key " << key << ", discarding value "
|
||||
<< old_value;
|
||||
}
|
||||
}
|
||||
|
||||
// Given a minidump file readable by |minidump_file_reader|, returns a map of
|
||||
// key-value pairs to use as HTTP form parameters for upload to a Breakpad
|
||||
// server. The map is built by combining the process simple annotations map with
|
||||
// each module’s simple annotations map. In the case of duplicate keys, the map
|
||||
// will retain the first value found for any key, and will log a warning about
|
||||
// discarded values. Each module’s annotations vector is also examined and built
|
||||
// into a single string value, with distinct elements separated by newlines, and
|
||||
// stored at the key named “list_annotations”, which supersedes any other key
|
||||
// found by that name. The client ID stored in the minidump is converted to
|
||||
// a string and stored at the key named “guid”, which supersedes any other key
|
||||
// found by that name.
|
||||
//
|
||||
// In the event of an error reading the minidump file, a message will be logged.
|
||||
std::map<std::string, std::string> BreakpadHTTPFormParametersFromMinidump(
|
||||
FileReaderInterface* minidump_file_reader) {
|
||||
ProcessSnapshotMinidump minidump_process_snapshot;
|
||||
if (!minidump_process_snapshot.Initialize(minidump_file_reader)) {
|
||||
return std::map<std::string, std::string>();
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> parameters =
|
||||
minidump_process_snapshot.AnnotationsSimpleMap();
|
||||
|
||||
std::string list_annotations;
|
||||
for (const ModuleSnapshot* module : minidump_process_snapshot.Modules()) {
|
||||
for (const auto& kv : module->AnnotationsSimpleMap()) {
|
||||
if (!parameters.insert(kv).second) {
|
||||
LOG(WARNING) << "duplicate key " << kv.first << ", discarding value "
|
||||
<< kv.second;
|
||||
}
|
||||
}
|
||||
|
||||
for (std::string annotation : module->AnnotationsVector()) {
|
||||
list_annotations.append(annotation);
|
||||
list_annotations.append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (!list_annotations.empty()) {
|
||||
// Remove the final newline character.
|
||||
list_annotations.resize(list_annotations.size() - 1);
|
||||
|
||||
InsertOrReplaceMapEntry(¶meters, "list_annotations", list_annotations);
|
||||
}
|
||||
|
||||
UUID client_id;
|
||||
minidump_process_snapshot.ClientID(&client_id);
|
||||
InsertOrReplaceMapEntry(¶meters, "guid", client_id.ToString());
|
||||
|
||||
return parameters;
|
||||
}
|
||||
|
||||
// Calls CrashReportDatabase::RecordUploadAttempt() with |successful| set to
|
||||
// false upon destruction unless disarmed by calling Fire() or Disarm(). Fire()
|
||||
// triggers an immediate call. Armed upon construction.
|
||||
class CallRecordUploadAttempt {
|
||||
public:
|
||||
CallRecordUploadAttempt(CrashReportDatabase* database,
|
||||
const CrashReportDatabase::Report* report)
|
||||
: database_(database),
|
||||
report_(report) {
|
||||
}
|
||||
|
||||
~CallRecordUploadAttempt() {
|
||||
Fire();
|
||||
}
|
||||
|
||||
void Fire() {
|
||||
if (report_) {
|
||||
database_->RecordUploadAttempt(report_, false, std::string());
|
||||
}
|
||||
|
||||
Disarm();
|
||||
}
|
||||
|
||||
void Disarm() {
|
||||
report_ = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
CrashReportDatabase* database_; // weak
|
||||
const CrashReportDatabase::Report* report_; // weak
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(CallRecordUploadAttempt);
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
CrashReportUploadThread::CrashReportUploadThread(CrashReportDatabase* database,
|
||||
const std::string& url,
|
||||
const Options& options)
|
||||
@ -157,11 +58,18 @@ CrashReportUploadThread::CrashReportUploadThread(CrashReportDatabase* database,
|
||||
: WorkerThread::kIndefiniteWait,
|
||||
this),
|
||||
known_pending_report_uuids_(),
|
||||
database_(database) {}
|
||||
database_(database) {
|
||||
DCHECK(!url_.empty());
|
||||
}
|
||||
|
||||
CrashReportUploadThread::~CrashReportUploadThread() {
|
||||
}
|
||||
|
||||
void CrashReportUploadThread::ReportPending(const UUID& report_uuid) {
|
||||
known_pending_report_uuids_.PushBack(report_uuid);
|
||||
thread_.DoWorkNow();
|
||||
}
|
||||
|
||||
void CrashReportUploadThread::Start() {
|
||||
thread_.Start(
|
||||
options_.watch_pending_reports ? 0.0 : WorkerThread::kIndefiniteWait);
|
||||
@ -171,11 +79,6 @@ void CrashReportUploadThread::Stop() {
|
||||
thread_.Stop();
|
||||
}
|
||||
|
||||
void CrashReportUploadThread::ReportPending(const UUID& report_uuid) {
|
||||
known_pending_report_uuids_.PushBack(report_uuid);
|
||||
thread_.DoWorkNow();
|
||||
}
|
||||
|
||||
void CrashReportUploadThread::ProcessPendingReports() {
|
||||
std::vector<UUID> known_report_uuids = known_pending_report_uuids_.Drain();
|
||||
for (const UUID& report_uuid : known_report_uuids) {
|
||||
@ -239,9 +142,8 @@ void CrashReportUploadThread::ProcessPendingReport(
|
||||
Settings* const settings = database_->GetSettings();
|
||||
|
||||
bool uploads_enabled;
|
||||
if (url_.empty() ||
|
||||
(!report.upload_explicitly_requested &&
|
||||
(!settings->GetUploadsEnabled(&uploads_enabled) || !uploads_enabled))) {
|
||||
if (!report.upload_explicitly_requested &&
|
||||
(!settings->GetUploadsEnabled(&uploads_enabled) || !uploads_enabled)) {
|
||||
// Don’t attempt an upload if there’s no URL to upload to. Allow upload if
|
||||
// it has been explicitly requested by the user, otherwise, respect the
|
||||
// upload-enabled state stored in the database’s settings.
|
||||
@ -290,7 +192,7 @@ void CrashReportUploadThread::ProcessPendingReport(
|
||||
}
|
||||
}
|
||||
|
||||
const CrashReportDatabase::Report* upload_report;
|
||||
std::unique_ptr<const CrashReportDatabase::UploadReport> upload_report;
|
||||
CrashReportDatabase::OperationStatus status =
|
||||
database_->GetReportForUploading(report.uuid, &upload_report);
|
||||
switch (status) {
|
||||
@ -317,18 +219,16 @@ void CrashReportUploadThread::ProcessPendingReport(
|
||||
return;
|
||||
}
|
||||
|
||||
CallRecordUploadAttempt call_record_upload_attempt(database_, upload_report);
|
||||
|
||||
std::string response_body;
|
||||
UploadResult upload_result = UploadReport(upload_report, &response_body);
|
||||
UploadResult upload_result =
|
||||
UploadReport(upload_report.get(), &response_body);
|
||||
switch (upload_result) {
|
||||
case UploadResult::kSuccess:
|
||||
call_record_upload_attempt.Disarm();
|
||||
database_->RecordUploadAttempt(upload_report, true, response_body);
|
||||
database_->RecordUploadComplete(std::move(upload_report), response_body);
|
||||
break;
|
||||
case UploadResult::kPermanentFailure:
|
||||
case UploadResult::kRetry:
|
||||
call_record_upload_attempt.Fire();
|
||||
upload_report.reset();
|
||||
|
||||
// TODO(mark): Deal with retries properly: don’t call SkipReportUplaod()
|
||||
// if the result was kRetry and the report hasn’t already been retried
|
||||
@ -340,17 +240,18 @@ void CrashReportUploadThread::ProcessPendingReport(
|
||||
}
|
||||
|
||||
CrashReportUploadThread::UploadResult CrashReportUploadThread::UploadReport(
|
||||
const CrashReportDatabase::Report* report,
|
||||
const CrashReportDatabase::UploadReport* report,
|
||||
std::string* response_body) {
|
||||
#if defined(OS_ANDROID)
|
||||
// TODO(jperaza): This method can be enabled on Android after HTTPTransport is
|
||||
// implemented and Crashpad takes over upload responsibilty on Android.
|
||||
NOTREACHED();
|
||||
return UploadResult::kPermanentFailure;
|
||||
#else
|
||||
std::map<std::string, std::string> parameters;
|
||||
|
||||
FileReader minidump_file_reader;
|
||||
if (!minidump_file_reader.Open(report->file_path)) {
|
||||
// If the minidump file can’t be opened, all hope is lost.
|
||||
return UploadResult::kPermanentFailure;
|
||||
}
|
||||
|
||||
FileOffset start_offset = minidump_file_reader.SeekGet();
|
||||
FileReader* reader = report->Reader();
|
||||
FileOffset start_offset = reader->SeekGet();
|
||||
if (start_offset < 0) {
|
||||
return UploadResult::kPermanentFailure;
|
||||
}
|
||||
@ -359,9 +260,13 @@ CrashReportUploadThread::UploadResult CrashReportUploadThread::UploadReport(
|
||||
// minidump file. This may result in its being uploaded with few or no
|
||||
// parameters, but as long as there’s a dump file, the server can decide what
|
||||
// to do with it.
|
||||
parameters = BreakpadHTTPFormParametersFromMinidump(&minidump_file_reader);
|
||||
ProcessSnapshotMinidump minidump_process_snapshot;
|
||||
if (minidump_process_snapshot.Initialize(reader)) {
|
||||
parameters =
|
||||
BreakpadHTTPFormParametersFromMinidump(&minidump_process_snapshot);
|
||||
}
|
||||
|
||||
if (!minidump_file_reader.SeekSet(start_offset)) {
|
||||
if (!reader->SeekSet(start_offset)) {
|
||||
return UploadResult::kPermanentFailure;
|
||||
}
|
||||
|
||||
@ -379,15 +284,15 @@ CrashReportUploadThread::UploadResult CrashReportUploadThread::UploadReport(
|
||||
}
|
||||
}
|
||||
|
||||
http_multipart_builder.SetFileAttachment(
|
||||
kMinidumpKey,
|
||||
#if defined(OS_WIN)
|
||||
base::UTF16ToUTF8(report->file_path.BaseName().value()),
|
||||
#else
|
||||
report->file_path.BaseName().value(),
|
||||
#endif
|
||||
&minidump_file_reader,
|
||||
"application/octet-stream");
|
||||
for (const auto& it : report->GetAttachments()) {
|
||||
http_multipart_builder.SetFileAttachment(
|
||||
it.first, it.first, it.second, "application/octet-stream");
|
||||
}
|
||||
|
||||
http_multipart_builder.SetFileAttachment(kMinidumpKey,
|
||||
report->uuid.ToString() + ".dmp",
|
||||
reader,
|
||||
"application/octet-stream");
|
||||
|
||||
std::unique_ptr<HTTPTransport> http_transport(HTTPTransport::Create());
|
||||
HTTPHeaders content_headers;
|
||||
@ -429,6 +334,7 @@ CrashReportUploadThread::UploadResult CrashReportUploadThread::UploadReport(
|
||||
}
|
||||
|
||||
return UploadResult::kSuccess;
|
||||
#endif // OS_ANDROID
|
||||
}
|
||||
|
||||
void CrashReportUploadThread::DoWork(const WorkerThread* thread) {
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include "client/crash_report_database.h"
|
||||
#include "util/misc/uuid.h"
|
||||
#include "util/stdlib/thread_safe_vector.h"
|
||||
#include "util/thread/stoppable.h"
|
||||
#include "util/thread/worker_thread.h"
|
||||
|
||||
namespace crashpad {
|
||||
@ -39,7 +40,8 @@ namespace crashpad {
|
||||
//! It also catches reports that are added without a ReportPending() signal
|
||||
//! being caught. This may happen if crash reports are added to the database by
|
||||
//! other processes.
|
||||
class CrashReportUploadThread : public WorkerThread::Delegate {
|
||||
class CrashReportUploadThread : public WorkerThread::Delegate,
|
||||
public Stoppable {
|
||||
public:
|
||||
//! \brief Options to be passed to the CrashReportUploadThread constructor.
|
||||
struct Options {
|
||||
@ -70,11 +72,22 @@ class CrashReportUploadThread : public WorkerThread::Delegate {
|
||||
const Options& options);
|
||||
~CrashReportUploadThread();
|
||||
|
||||
//! \brief Informs the upload thread that a new pending report has been added
|
||||
//! to the database.
|
||||
//!
|
||||
//! \param[in] report_uuid The unique identifier of the newly added pending
|
||||
//! report.
|
||||
//!
|
||||
//! This method may be called from any thread.
|
||||
void ReportPending(const UUID& report_uuid);
|
||||
|
||||
// Stoppable:
|
||||
|
||||
//! \brief Starts a dedicated upload thread, which executes ThreadMain().
|
||||
//!
|
||||
//! This method may only be be called on a newly-constructed object or after
|
||||
//! a call to Stop().
|
||||
void Start();
|
||||
void Start() override;
|
||||
|
||||
//! \brief Stops the upload thread.
|
||||
//!
|
||||
@ -88,16 +101,7 @@ class CrashReportUploadThread : public WorkerThread::Delegate {
|
||||
//!
|
||||
//! This method may be called from any thread other than the upload thread.
|
||||
//! It is expected to only be called from the same thread that called Start().
|
||||
void Stop();
|
||||
|
||||
//! \brief Informs the upload thread that a new pending report has been added
|
||||
//! to the database.
|
||||
//!
|
||||
//! \param[in] report_uuid The unique identifier of the newly added pending
|
||||
//! report.
|
||||
//!
|
||||
//! This method may be called from any thread.
|
||||
void ReportPending(const UUID& report_uuid);
|
||||
void Stop() override;
|
||||
|
||||
private:
|
||||
//! \brief The result code from UploadReport().
|
||||
@ -148,14 +152,14 @@ class CrashReportUploadThread : public WorkerThread::Delegate {
|
||||
//! \param[in] report The report to upload. The caller is responsible for
|
||||
//! calling CrashReportDatabase::GetReportForUploading() before calling
|
||||
//! this method, and for calling
|
||||
//! CrashReportDatabase::RecordUploadAttempt() after calling this method.
|
||||
//! CrashReportDatabase::RecordUploadComplete() after calling this method.
|
||||
//! \param[out] response_body If the upload attempt is successful, this will
|
||||
//! be set to the response body sent by the server. Breakpad-type servers
|
||||
//! provide the crash ID assigned by the server in the response body.
|
||||
//!
|
||||
//! \return A member of UploadResult indicating the result of the upload
|
||||
//! attempt.
|
||||
UploadResult UploadReport(const CrashReportDatabase::Report* report,
|
||||
UploadResult UploadReport(const CrashReportDatabase::UploadReport* report,
|
||||
std::string* response_body);
|
||||
|
||||
// WorkerThread::Delegate:
|
||||
|
@ -73,6 +73,13 @@ when run normally from a shell using only the basename (without an explicit
|
||||
stdio will be hooked up as expected to the parent console so that logging output
|
||||
will be visible.
|
||||
|
||||
On Linux/Android, the handler may create a crash dump for its parent process
|
||||
using **--trace-parent-with-exception**. In this mode, the handler process
|
||||
creates a crash dump for its parent and exits. Alternatively, the handler may
|
||||
be launched with **--initial-client-fd** which will start the server connected
|
||||
to an initial client. The server will exit when all connected client sockets are
|
||||
closed.
|
||||
|
||||
It is not normally appropriate to invoke this program directly. Usually, it will
|
||||
be invoked by a Crashpad client using the Crashpad client library, or started by
|
||||
another system service. On macOS, arbitrary programs may be run with a Crashpad
|
||||
@ -238,6 +245,18 @@ establish the Crashpad client environment before running a program.
|
||||
parent process. This option is only valid on macOS. Use of this option is
|
||||
discouraged. It should not be used absent extraordinary circumstances.
|
||||
|
||||
* **--trace-parent-with-exception**=_EXCEPTION-INFORMATION-ADDRESS_
|
||||
|
||||
Causes the handler process to trace its parent process and exit. The parent
|
||||
process should have an ExceptionInformation struct at
|
||||
_EXCEPTION-INFORMATION-ADDRESS_.
|
||||
|
||||
* **--initial-client-fd**=_FD_
|
||||
|
||||
Starts the excetion handler server with an initial ExceptionHandlerClient
|
||||
connected on the socket _FD_. The server will exit when all connected client
|
||||
sockets have been closed.
|
||||
|
||||
* **--url**=_URL_
|
||||
|
||||
If uploads are enabled, sends crash reports to the Breakpad-type crash report
|
||||
|
@ -28,7 +28,7 @@
|
||||
#include "test/test_paths.h"
|
||||
#include "test/win/win_multiprocess_with_temp_dir.h"
|
||||
#include "util/file/file_reader.h"
|
||||
#include "util/win/capture_context.h"
|
||||
#include "util/misc/capture_context.h"
|
||||
|
||||
namespace crashpad {
|
||||
namespace test {
|
||||
@ -93,7 +93,7 @@ void CrashWithExtendedHandler::ValidateGeneratedDump() {
|
||||
ASSERT_TRUE(database);
|
||||
|
||||
std::vector<CrashReportDatabase::Report> reports;
|
||||
ASSERT_EQ(database->GetCompletedReports(&reports),
|
||||
ASSERT_EQ(database->GetPendingReports(&reports),
|
||||
CrashReportDatabase::kNoError);
|
||||
ASSERT_EQ(reports.size(), 1u);
|
||||
|
||||
@ -133,7 +133,13 @@ void CrashWithExtendedHandler::ValidateGeneratedDump() {
|
||||
EXPECT_EQ(found_extension_streams, 1u);
|
||||
}
|
||||
|
||||
TEST(CrashpadHandler, ExtensibilityCalloutsWork) {
|
||||
#if defined(ADDRESS_SANITIZER)
|
||||
// https://crbug.com/845011
|
||||
#define MAYBE_ExtensibilityCalloutsWork DISABLED_ExtensibilityCalloutsWork
|
||||
#else
|
||||
#define MAYBE_ExtensibilityCalloutsWork ExtensibilityCalloutsWork
|
||||
#endif
|
||||
TEST(CrashpadHandler, MAYBE_ExtensibilityCalloutsWork) {
|
||||
WinMultiprocessWithTempDir::Run<CrashWithExtendedHandler>();
|
||||
}
|
||||
|
||||
|
@ -56,7 +56,7 @@ int ExtendedHandlerMain(int argc, char* argv[]) {
|
||||
|
||||
} // namespace
|
||||
|
||||
#if defined(OS_MACOSX)
|
||||
#if defined(OS_POSIX)
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
return ExtendedHandlerMain(argc, argv);
|
||||
@ -68,4 +68,4 @@ int wmain(int argc, wchar_t* argv[]) {
|
||||
return crashpad::ToolSupport::Wmain(argc, argv, &ExtendedHandlerMain);
|
||||
}
|
||||
|
||||
#endif // OS_MACOSX
|
||||
#endif // OS_POSIX
|
||||
|
182
handler/fuchsia/crash_report_exception_handler.cc
Normal file
182
handler/fuchsia/crash_report_exception_handler.cc
Normal file
@ -0,0 +1,182 @@
|
||||
// Copyright 2017 The Crashpad Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "handler/fuchsia/crash_report_exception_handler.h"
|
||||
|
||||
#include <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
|
105
handler/fuchsia/crash_report_exception_handler.h
Normal file
105
handler/fuchsia/crash_report_exception_handler.h
Normal file
@ -0,0 +1,105 @@
|
||||
// Copyright 2017 The Crashpad Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef CRASHPAD_HANDLER_FUCHSIA_CRASH_REPORT_EXCEPTION_HANDLER_H_
|
||||
#define CRASHPAD_HANDLER_FUCHSIA_CRASH_REPORT_EXCEPTION_HANDLER_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <zircon/types.h>
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/macros.h"
|
||||
#include "client/crash_report_database.h"
|
||||
#include "handler/crash_report_upload_thread.h"
|
||||
#include "handler/user_stream_data_source.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
//! \brief An exception handler that writes crash reports for exception messages
|
||||
//! to a CrashReportDatabase.
|
||||
class CrashReportExceptionHandler {
|
||||
public:
|
||||
//! \brief Creates a new object that will store crash reports in \a database.
|
||||
//!
|
||||
//! \param[in] database The database to store crash reports in. Weak.
|
||||
//! \param[in] upload_thread The upload thread to notify when a new crash
|
||||
//! report is written into \a database.
|
||||
//! \param[in] process_annotations A map of annotations to insert as
|
||||
//! process-level annotations into each crash report that is written. Do
|
||||
//! not confuse this with module-level annotations, which are under the
|
||||
//! control of the crashing process, and are used to implement Chrome's
|
||||
//! "crash keys." Process-level annotations are those that are beyond the
|
||||
//! control of the crashing process, which must reliably be set even if
|
||||
//! the process crashes before it’s able to establish its own annotations.
|
||||
//! To interoperate with Breakpad servers, the recommended practice is to
|
||||
//! specify values for the `"prod"` and `"ver"` keys as process
|
||||
//! annotations.
|
||||
//! \param[in] process_attachments A map of file name keys to file paths to be
|
||||
//! included in the report. Each time a report is written, the file paths
|
||||
//! will be read in their entirety and included in the report using the
|
||||
//! file name key as the name in the http upload.
|
||||
//! \param[in] user_stream_data_sources Data sources to be used to extend
|
||||
//! crash reports. For each crash report that is written, the data sources
|
||||
//! are called in turn. These data sources may contribute additional
|
||||
//! minidump streams. `nullptr` if not required.
|
||||
CrashReportExceptionHandler(
|
||||
CrashReportDatabase* database,
|
||||
CrashReportUploadThread* upload_thread,
|
||||
const std::map<std::string, std::string>* process_annotations,
|
||||
const std::map<std::string, base::FilePath>* process_attachments,
|
||||
const UserStreamDataSources* user_stream_data_sources);
|
||||
|
||||
~CrashReportExceptionHandler();
|
||||
|
||||
//! \brief Called when the exception handler server has caught an exception
|
||||
//! and wants a crash dump to be taken.
|
||||
//!
|
||||
//! This function is expected to call `zx_task_resume()` in order to complete
|
||||
//! handling of the exception.
|
||||
//!
|
||||
//! \param[in] process_id The koid of the process which sustained the
|
||||
//! exception.
|
||||
//! \param[in] thread_id The koid of the thread which sustained the exception.
|
||||
//! \return `true` on success, or `false` with an error logged.
|
||||
bool HandleException(uint64_t process_id, uint64_t thread_id);
|
||||
|
||||
//! \brief Called when the exception handler server has caught an exception
|
||||
//! and wants a crash dump to be taken.
|
||||
//!
|
||||
//! This function is expected to call `zx_task_resume()` in order to complete
|
||||
//! handling of the exception.
|
||||
//!
|
||||
//! \param[in] process The handle to the process which sustained the
|
||||
//! exception.
|
||||
//! \param[in] thread The handle to the thread of \a process which sustained
|
||||
//! the exception.
|
||||
//! \return `true` on success, or `false` with an error logged.
|
||||
bool HandleExceptionHandles(zx_handle_t process, zx_handle_t thread);
|
||||
|
||||
private:
|
||||
CrashReportDatabase* database_; // weak
|
||||
CrashReportUploadThread* upload_thread_; // weak
|
||||
const std::map<std::string, std::string>* process_annotations_; // weak
|
||||
const std::map<std::string, base::FilePath>* process_attachments_; // weak
|
||||
const UserStreamDataSources* user_stream_data_sources_; // weak
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(CrashReportExceptionHandler);
|
||||
};
|
||||
|
||||
} // namespace crashpad
|
||||
|
||||
#endif // CRASHPAD_HANDLER_FUCHSIA_CRASH_REPORT_EXCEPTION_HANDLER_H_
|
60
handler/fuchsia/exception_handler_server.cc
Normal file
60
handler/fuchsia/exception_handler_server.cc
Normal file
@ -0,0 +1,60 @@
|
||||
// Copyright 2017 The Crashpad Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "handler/fuchsia/exception_handler_server.h"
|
||||
|
||||
#include <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
|
55
handler/fuchsia/exception_handler_server.h
Normal file
55
handler/fuchsia/exception_handler_server.h
Normal file
@ -0,0 +1,55 @@
|
||||
// Copyright 2017 The Crashpad Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef CRASHPAD_HANDLER_FUCHSIA_EXCEPTION_HANDLER_SERVER_H_
|
||||
#define CRASHPAD_HANDLER_FUCHSIA_EXCEPTION_HANDLER_SERVER_H_
|
||||
|
||||
#include "base/macros.h"
|
||||
#include "base/fuchsia/scoped_zx_handle.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
class CrashReportExceptionHandler;
|
||||
|
||||
//! \brief Runs the main exception-handling server in Crashpad's handler
|
||||
//! process.
|
||||
class ExceptionHandlerServer {
|
||||
public:
|
||||
//! \brief Constructs an ExceptionHandlerServer object.
|
||||
//!
|
||||
//! \param[in] root_job The root of the tree of processes that will be handled
|
||||
//! by this server. It is assumed that \a exception_port is the exception
|
||||
//! port of this job.
|
||||
//! \param[in] exception_port The exception port that this server will
|
||||
//! monitor.
|
||||
ExceptionHandlerServer(base::ScopedZxHandle root_job,
|
||||
base::ScopedZxHandle exception_port);
|
||||
~ExceptionHandlerServer();
|
||||
|
||||
//! \brief Runs the exception-handling server.
|
||||
//!
|
||||
//! \param[in] handler The handler to which the exceptions are delegated when
|
||||
//! they are caught in Run(). Ownership is not transferred.
|
||||
void Run(CrashReportExceptionHandler* handler);
|
||||
|
||||
private:
|
||||
base::ScopedZxHandle root_job_;
|
||||
base::ScopedZxHandle exception_port_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ExceptionHandlerServer);
|
||||
};
|
||||
|
||||
} // namespace crashpad
|
||||
|
||||
#endif // CRASHPAD_HANDLER_FUCHSIA_EXCEPTION_HANDLER_SERVER_H_
|
@ -39,12 +39,18 @@
|
||||
'crash_report_upload_thread.h',
|
||||
'handler_main.cc',
|
||||
'handler_main.h',
|
||||
'linux/crash_report_exception_handler.cc',
|
||||
'linux/crash_report_exception_handler.h',
|
||||
'linux/exception_handler_server.cc',
|
||||
'linux/exception_handler_server.h',
|
||||
'mac/crash_report_exception_handler.cc',
|
||||
'mac/crash_report_exception_handler.h',
|
||||
'mac/exception_handler_server.cc',
|
||||
'mac/exception_handler_server.h',
|
||||
'mac/file_limit_annotation.cc',
|
||||
'mac/file_limit_annotation.h',
|
||||
'minidump_to_upload_parameters.cc',
|
||||
'minidump_to_upload_parameters.h',
|
||||
'prune_crash_reports_thread.cc',
|
||||
'prune_crash_reports_thread.h',
|
||||
'user_stream_data_source.cc',
|
||||
@ -52,6 +58,13 @@
|
||||
'win/crash_report_exception_handler.cc',
|
||||
'win/crash_report_exception_handler.h',
|
||||
],
|
||||
'target_conditions': [
|
||||
['OS=="android"', {
|
||||
'sources/': [
|
||||
['include', '^linux/'],
|
||||
],
|
||||
}],
|
||||
],
|
||||
},
|
||||
{
|
||||
'target_name': 'crashpad_handler',
|
||||
|
@ -34,6 +34,7 @@
|
||||
#include "base/logging.h"
|
||||
#include "base/metrics/persistent_histogram_allocator.h"
|
||||
#include "base/scoped_generic.h"
|
||||
#include "base/strings/string_number_conversions.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "build/build_config.h"
|
||||
@ -46,6 +47,7 @@
|
||||
#include "handler/prune_crash_reports_thread.h"
|
||||
#include "tools/tool_support.h"
|
||||
#include "util/file/file_io.h"
|
||||
#include "util/misc/address_types.h"
|
||||
#include "util/misc/metrics.h"
|
||||
#include "util/misc/paths.h"
|
||||
#include "util/numeric/in_range_cast.h"
|
||||
@ -54,7 +56,13 @@
|
||||
#include "util/string/split_string.h"
|
||||
#include "util/synchronization/semaphore.h"
|
||||
|
||||
#if defined(OS_MACOSX)
|
||||
#if defined(OS_LINUX) || defined(OS_ANDROID)
|
||||
#include <unistd.h>
|
||||
|
||||
#include "handler/linux/crash_report_exception_handler.h"
|
||||
#include "handler/linux/exception_handler_server.h"
|
||||
#include "util/posix/signals.h"
|
||||
#elif defined(OS_MACOSX)
|
||||
#include <libgen.h>
|
||||
#include <signal.h>
|
||||
|
||||
@ -74,6 +82,15 @@
|
||||
#include "util/win/handle.h"
|
||||
#include "util/win/initial_client_data.h"
|
||||
#include "util/win/session_end_watcher.h"
|
||||
#elif defined(OS_FUCHSIA)
|
||||
#include <zircon/process.h>
|
||||
#include <zircon/processargs.h>
|
||||
|
||||
#include "handler/fuchsia/crash_report_exception_handler.h"
|
||||
#include "handler/fuchsia/exception_handler_server.h"
|
||||
#elif defined(OS_LINUX)
|
||||
#include "handler/linux/crash_report_exception_handler.h"
|
||||
#include "handler/linux/exception_handler_server.h"
|
||||
#endif // OS_MACOSX
|
||||
|
||||
namespace crashpad {
|
||||
@ -123,6 +140,13 @@ void Usage(const base::FilePath& me) {
|
||||
" --reset-own-crash-exception-port-to-system-default\n"
|
||||
" reset the server's exception handler to default\n"
|
||||
#endif // OS_MACOSX
|
||||
#if defined(OS_LINUX) || defined(OS_ANDROID)
|
||||
" --trace-parent-with-exception=EXCEPTION_INFORMATION_ADDRESS\n"
|
||||
" request a dump for the handler's parent process\n"
|
||||
" --initial-client-fd=FD a socket connected to a client.\n"
|
||||
" --sanitization_information=SANITIZATION_INFORMATION_ADDRESS\n"
|
||||
" the address of a SanitizationInformation struct.\n"
|
||||
#endif // OS_LINUX || OS_ANDROID
|
||||
" --url=URL send crash reports to this Breakpad server URL,\n"
|
||||
" only if uploads are enabled for the database\n"
|
||||
" --help display this help and exit\n"
|
||||
@ -142,6 +166,10 @@ struct Options {
|
||||
std::string mach_service;
|
||||
int handshake_fd;
|
||||
bool reset_own_crash_exception_port_to_system_default;
|
||||
#elif defined(OS_LINUX) || defined(OS_ANDROID)
|
||||
VMAddress exception_information_address;
|
||||
int initial_client_fd;
|
||||
VMAddress sanitization_information_address;
|
||||
#elif defined(OS_WIN)
|
||||
std::string pipe_name;
|
||||
InitialClientData initial_client_data;
|
||||
@ -208,7 +236,9 @@ class CallMetricsRecordNormalExit {
|
||||
DISALLOW_COPY_AND_ASSIGN(CallMetricsRecordNormalExit);
|
||||
};
|
||||
|
||||
#if defined(OS_MACOSX)
|
||||
#if defined(OS_MACOSX) || defined(OS_LINUX) || defined(OS_ANDROID)
|
||||
|
||||
Signals::OldActions g_old_crash_signal_handlers;
|
||||
|
||||
void HandleCrashSignal(int sig, siginfo_t* siginfo, void* context) {
|
||||
MetricsRecordExit(Metrics::LifetimeMilestone::kCrashed);
|
||||
@ -244,7 +274,9 @@ void HandleCrashSignal(int sig, siginfo_t* siginfo, void* context) {
|
||||
}
|
||||
Metrics::HandlerCrashed(metrics_code);
|
||||
|
||||
Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, nullptr);
|
||||
struct sigaction* old_action =
|
||||
g_old_crash_signal_handlers.ActionForSignal(sig);
|
||||
Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, old_action);
|
||||
}
|
||||
|
||||
void HandleTerminateSignal(int sig, siginfo_t* siginfo, void* context) {
|
||||
@ -252,6 +284,8 @@ void HandleTerminateSignal(int sig, siginfo_t* siginfo, void* context) {
|
||||
Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, nullptr);
|
||||
}
|
||||
|
||||
#if defined(OS_MACOSX)
|
||||
|
||||
void ReinstallCrashHandler() {
|
||||
// This is used to re-enable the metrics-recording crash handler after
|
||||
// MonitorSelf() sets up a Crashpad exception handler. On macOS, the
|
||||
@ -290,6 +324,23 @@ void HandleSIGTERM(int sig, siginfo_t* siginfo, void* context) {
|
||||
g_exception_handler_server->Stop();
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void ReinstallCrashHandler() {
|
||||
// This is used to re-enable the metrics-recording crash handler after
|
||||
// MonitorSelf() sets up a Crashpad signal handler.
|
||||
Signals::InstallCrashHandlers(
|
||||
HandleCrashSignal, 0, &g_old_crash_signal_handlers);
|
||||
}
|
||||
|
||||
void InstallCrashHandler() {
|
||||
ReinstallCrashHandler();
|
||||
|
||||
Signals::InstallTerminateHandlers(HandleTerminateSignal, 0, nullptr);
|
||||
}
|
||||
|
||||
#endif // OS_MACOSX
|
||||
|
||||
#elif defined(OS_WIN)
|
||||
|
||||
LONG(WINAPI* g_original_exception_filter)(EXCEPTION_POINTERS*) = nullptr;
|
||||
@ -345,6 +396,22 @@ void InstallCrashHandler() {
|
||||
ALLOW_UNUSED_LOCAL(terminate_handler);
|
||||
}
|
||||
|
||||
#elif defined(OS_FUCHSIA)
|
||||
|
||||
void InstallCrashHandler() {
|
||||
// There's nothing to do here. Crashes in this process will already be caught
|
||||
// here because this handler process is in the same job that has had its
|
||||
// exception port bound.
|
||||
|
||||
// TODO(scottmg): This should collect metrics on handler crashes, at a
|
||||
// minimum. https://crashpad.chromium.org/bug/230.
|
||||
}
|
||||
|
||||
void ReinstallCrashHandler() {
|
||||
// TODO(scottmg): Fuchsia: https://crashpad.chromium.org/bug/196
|
||||
NOTREACHED();
|
||||
}
|
||||
|
||||
#endif // OS_MACOSX
|
||||
|
||||
void MonitorSelf(const Options& options) {
|
||||
@ -381,6 +448,16 @@ void MonitorSelf(const Options& options) {
|
||||
// instance of crashpad_handler to be writing metrics at a time, and it should
|
||||
// be the primary instance.
|
||||
CrashpadClient crashpad_client;
|
||||
#if defined(OS_LINUX) || defined(OS_ANDROID)
|
||||
if (!crashpad_client.StartHandlerAtCrash(executable_path,
|
||||
options.database,
|
||||
base::FilePath(),
|
||||
options.url,
|
||||
options.annotations,
|
||||
extra_arguments)) {
|
||||
return;
|
||||
}
|
||||
#else
|
||||
if (!crashpad_client.StartHandler(executable_path,
|
||||
options.database,
|
||||
base::FilePath(),
|
||||
@ -391,12 +468,33 @@ void MonitorSelf(const Options& options) {
|
||||
false)) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Make sure that appropriate metrics will be recorded on crash before this
|
||||
// process is terminated.
|
||||
ReinstallCrashHandler();
|
||||
}
|
||||
|
||||
class ScopedStoppable {
|
||||
public:
|
||||
ScopedStoppable() = default;
|
||||
|
||||
~ScopedStoppable() {
|
||||
if (stoppable_) {
|
||||
stoppable_->Stop();
|
||||
}
|
||||
}
|
||||
|
||||
void Reset(Stoppable* stoppable) { stoppable_.reset(stoppable); }
|
||||
|
||||
Stoppable* Get() { return stoppable_.get(); }
|
||||
|
||||
private:
|
||||
std::unique_ptr<Stoppable> stoppable_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ScopedStoppable);
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
int HandlerMain(int argc,
|
||||
@ -437,6 +535,11 @@ int HandlerMain(int argc,
|
||||
#if defined(OS_MACOSX)
|
||||
kOptionResetOwnCrashExceptionPortToSystemDefault,
|
||||
#endif // OS_MACOSX
|
||||
#if defined(OS_LINUX) || defined(OS_ANDROID)
|
||||
kOptionTraceParentWithException,
|
||||
kOptionInitialClientFD,
|
||||
kOptionSanitizationInformation,
|
||||
#endif
|
||||
kOptionURL,
|
||||
|
||||
// Standard options.
|
||||
@ -485,6 +588,17 @@ int HandlerMain(int argc,
|
||||
nullptr,
|
||||
kOptionResetOwnCrashExceptionPortToSystemDefault},
|
||||
#endif // OS_MACOSX
|
||||
#if defined(OS_LINUX) || defined(OS_ANDROID)
|
||||
{"trace-parent-with-exception",
|
||||
required_argument,
|
||||
nullptr,
|
||||
kOptionTraceParentWithException},
|
||||
{"initial-client-fd", required_argument, nullptr, kOptionInitialClientFD},
|
||||
{"sanitization-information",
|
||||
required_argument,
|
||||
nullptr,
|
||||
kOptionSanitizationInformation},
|
||||
#endif // OS_LINUX || OS_ANDROID
|
||||
{"url", required_argument, nullptr, kOptionURL},
|
||||
{"help", no_argument, nullptr, kOptionHelp},
|
||||
{"version", no_argument, nullptr, kOptionVersion},
|
||||
@ -499,6 +613,11 @@ int HandlerMain(int argc,
|
||||
options.periodic_tasks = true;
|
||||
options.rate_limit = true;
|
||||
options.upload_gzip = true;
|
||||
#if defined(OS_LINUX) || defined(OS_ANDROID)
|
||||
options.exception_information_address = 0;
|
||||
options.initial_client_fd = kInvalidFileHandle;
|
||||
options.sanitization_information_address = 0;
|
||||
#endif
|
||||
|
||||
int opt;
|
||||
while ((opt = getopt_long(argc, argv, "", long_options, nullptr)) != -1) {
|
||||
@ -588,6 +707,32 @@ int HandlerMain(int argc,
|
||||
break;
|
||||
}
|
||||
#endif // OS_MACOSX
|
||||
#if defined(OS_LINUX) || defined(OS_ANDROID)
|
||||
case kOptionTraceParentWithException: {
|
||||
if (!StringToNumber(optarg, &options.exception_information_address)) {
|
||||
ToolSupport::UsageHint(
|
||||
me, "failed to parse --trace-parent-with-exception");
|
||||
return ExitFailure();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kOptionInitialClientFD: {
|
||||
if (!base::StringToInt(optarg, &options.initial_client_fd)) {
|
||||
ToolSupport::UsageHint(me, "failed to parse --initial-client-fd");
|
||||
return ExitFailure();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kOptionSanitizationInformation: {
|
||||
if (!StringToNumber(optarg,
|
||||
&options.sanitization_information_address)) {
|
||||
ToolSupport::UsageHint(me,
|
||||
"failed to parse --sanitization-information");
|
||||
return ExitFailure();
|
||||
}
|
||||
break;
|
||||
}
|
||||
#endif // OS_LINUX || OS_ANDROID
|
||||
case kOptionURL: {
|
||||
options.url = optarg;
|
||||
break;
|
||||
@ -632,6 +777,20 @@ int HandlerMain(int argc,
|
||||
me, "--initial-client-data and --pipe-name are incompatible");
|
||||
return ExitFailure();
|
||||
}
|
||||
#elif defined(OS_LINUX) || defined(OS_ANDROID)
|
||||
if (!options.exception_information_address &&
|
||||
options.initial_client_fd == kInvalidFileHandle) {
|
||||
ToolSupport::UsageHint(
|
||||
me, "--trace-parent-with-exception or --initial_client_fd is required");
|
||||
return ExitFailure();
|
||||
}
|
||||
if (options.sanitization_information_address &&
|
||||
!options.exception_information_address) {
|
||||
ToolSupport::UsageHint(
|
||||
me,
|
||||
"--sanitization_information requires --trace-parent-with-exception");
|
||||
return ExitFailure();
|
||||
}
|
||||
#endif // OS_MACOSX
|
||||
|
||||
if (options.database.empty()) {
|
||||
@ -674,6 +833,57 @@ int HandlerMain(int argc,
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<CrashReportDatabase> database(
|
||||
CrashReportDatabase::Initialize(options.database));
|
||||
if (!database) {
|
||||
return ExitFailure();
|
||||
}
|
||||
|
||||
ScopedStoppable upload_thread;
|
||||
if (!options.url.empty()) {
|
||||
// TODO(scottmg): options.rate_limit should be removed when we have a
|
||||
// configurable database setting to control upload limiting.
|
||||
// See https://crashpad.chromium.org/bug/23.
|
||||
CrashReportUploadThread::Options upload_thread_options;
|
||||
upload_thread_options.identify_client_via_url =
|
||||
options.identify_client_via_url;
|
||||
upload_thread_options.rate_limit = options.rate_limit;
|
||||
upload_thread_options.upload_gzip = options.upload_gzip;
|
||||
upload_thread_options.watch_pending_reports = options.periodic_tasks;
|
||||
|
||||
upload_thread.Reset(new CrashReportUploadThread(
|
||||
database.get(), options.url, upload_thread_options));
|
||||
upload_thread.Get()->Start();
|
||||
}
|
||||
|
||||
CrashReportExceptionHandler exception_handler(
|
||||
database.get(),
|
||||
static_cast<CrashReportUploadThread*>(upload_thread.Get()),
|
||||
&options.annotations,
|
||||
#if defined(OS_FUCHSIA)
|
||||
// TODO(scottmg): Process level file attachments, and for all platforms.
|
||||
nullptr,
|
||||
#endif
|
||||
user_stream_sources);
|
||||
|
||||
#if defined(OS_LINUX) || defined(OS_ANDROID)
|
||||
if (options.exception_information_address) {
|
||||
ClientInformation info;
|
||||
info.exception_information_address = options.exception_information_address;
|
||||
info.sanitization_information_address =
|
||||
options.sanitization_information_address;
|
||||
return exception_handler.HandleException(getppid(), info) ? EXIT_SUCCESS
|
||||
: ExitFailure();
|
||||
}
|
||||
#endif // OS_LINUX || OS_ANDROID
|
||||
|
||||
ScopedStoppable prune_thread;
|
||||
if (options.periodic_tasks) {
|
||||
prune_thread.Reset(new PruneCrashReportThread(
|
||||
database.get(), PruneCondition::GetDefault()));
|
||||
prune_thread.Get()->Start();
|
||||
}
|
||||
|
||||
#if defined(OS_MACOSX)
|
||||
if (options.mach_service.empty()) {
|
||||
// Don’t do this when being run by launchd. See launchd.plist(5).
|
||||
@ -727,6 +937,29 @@ int HandlerMain(int argc,
|
||||
if (!options.pipe_name.empty()) {
|
||||
exception_handler_server.SetPipeName(base::UTF8ToUTF16(options.pipe_name));
|
||||
}
|
||||
#elif defined(OS_FUCHSIA)
|
||||
// These handles are logically "moved" into these variables when retrieved by
|
||||
// zx_take_startup_handle(). Both are given to ExceptionHandlerServer which
|
||||
// owns them in this process. There is currently no "connect-later" mode on
|
||||
// Fuchsia, all the binding must be done by the client before starting
|
||||
// crashpad_handler.
|
||||
base::ScopedZxHandle root_job(zx_take_startup_handle(PA_HND(PA_USER0, 0)));
|
||||
if (!root_job.is_valid()) {
|
||||
LOG(ERROR) << "no process handle passed in startup handle 0";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
base::ScopedZxHandle exception_port(
|
||||
zx_take_startup_handle(PA_HND(PA_USER0, 1)));
|
||||
if (!exception_port.is_valid()) {
|
||||
LOG(ERROR) << "no exception port handle passed in startup handle 1";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
ExceptionHandlerServer exception_handler_server(std::move(root_job),
|
||||
std::move(exception_port));
|
||||
#elif defined(OS_LINUX) || defined(OS_ANDROID)
|
||||
ExceptionHandlerServer exception_handler_server;
|
||||
#endif // OS_MACOSX
|
||||
|
||||
base::GlobalHistogramAllocator* histogram_allocator = nullptr;
|
||||
@ -742,52 +975,21 @@ int HandlerMain(int argc,
|
||||
|
||||
Metrics::HandlerLifetimeMilestone(Metrics::LifetimeMilestone::kStarted);
|
||||
|
||||
std::unique_ptr<CrashReportDatabase> database(
|
||||
CrashReportDatabase::Initialize(options.database));
|
||||
if (!database) {
|
||||
return ExitFailure();
|
||||
}
|
||||
|
||||
// TODO(scottmg): options.rate_limit should be removed when we have a
|
||||
// configurable database setting to control upload limiting.
|
||||
// See https://crashpad.chromium.org/bug/23.
|
||||
CrashReportUploadThread::Options upload_thread_options;
|
||||
upload_thread_options.identify_client_via_url =
|
||||
options.identify_client_via_url;
|
||||
upload_thread_options.rate_limit = options.rate_limit;
|
||||
upload_thread_options.upload_gzip = options.upload_gzip;
|
||||
upload_thread_options.watch_pending_reports = options.periodic_tasks;
|
||||
CrashReportUploadThread upload_thread(database.get(),
|
||||
options.url,
|
||||
upload_thread_options);
|
||||
upload_thread.Start();
|
||||
|
||||
std::unique_ptr<PruneCrashReportThread> prune_thread;
|
||||
if (options.periodic_tasks) {
|
||||
prune_thread.reset(new PruneCrashReportThread(
|
||||
database.get(), PruneCondition::GetDefault()));
|
||||
prune_thread->Start();
|
||||
}
|
||||
|
||||
CrashReportExceptionHandler exception_handler(database.get(),
|
||||
&upload_thread,
|
||||
&options.annotations,
|
||||
user_stream_sources);
|
||||
|
||||
#if defined(OS_WIN)
|
||||
if (options.initial_client_data.IsValid()) {
|
||||
exception_handler_server.InitializeWithInheritedDataForInitialClient(
|
||||
options.initial_client_data, &exception_handler);
|
||||
}
|
||||
#elif defined(OS_LINUX) || defined(OS_ANDROID)
|
||||
if (options.initial_client_fd == kInvalidFileHandle ||
|
||||
!exception_handler_server.InitializeWithClient(
|
||||
ScopedFileHandle(options.initial_client_fd))) {
|
||||
return ExitFailure();
|
||||
}
|
||||
#endif // OS_WIN
|
||||
|
||||
exception_handler_server.Run(&exception_handler);
|
||||
|
||||
upload_thread.Stop();
|
||||
if (prune_thread) {
|
||||
prune_thread->Stop();
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,48 @@
|
||||
'../build/crashpad.gypi',
|
||||
],
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'crashpad_handler_test',
|
||||
'type': 'executable',
|
||||
'dependencies': [
|
||||
'crashpad_handler_test_extended_handler',
|
||||
'handler.gyp:crashpad_handler_lib',
|
||||
'../client/client.gyp:crashpad_client',
|
||||
'../compat/compat.gyp:crashpad_compat',
|
||||
'../snapshot/snapshot.gyp:crashpad_snapshot',
|
||||
'../snapshot/snapshot_test.gyp:crashpad_snapshot_test_lib',
|
||||
'../test/test.gyp:crashpad_gtest_main',
|
||||
'../test/test.gyp:crashpad_test',
|
||||
'../third_party/gtest/gtest.gyp:gtest',
|
||||
'../third_party/mini_chromium/mini_chromium.gyp:base',
|
||||
'../util/util.gyp:crashpad_util',
|
||||
],
|
||||
'include_dirs': [
|
||||
'..',
|
||||
],
|
||||
'sources': [
|
||||
'crashpad_handler_test.cc',
|
||||
'linux/exception_handler_server_test.cc',
|
||||
'minidump_to_upload_parameters_test.cc',
|
||||
],
|
||||
'conditions': [
|
||||
['OS!="win"', {
|
||||
'dependencies!': [
|
||||
'crashpad_handler_test_extended_handler',
|
||||
],
|
||||
'sources!': [
|
||||
'crashpad_handler_test.cc',
|
||||
],
|
||||
}],
|
||||
],
|
||||
'target_conditions': [
|
||||
['OS=="android"', {
|
||||
'sources/': [
|
||||
['include', '^linux/'],
|
||||
],
|
||||
}],
|
||||
],
|
||||
},
|
||||
{
|
||||
'target_name': 'crashpad_handler_test_extended_handler',
|
||||
'type': 'executable',
|
||||
@ -52,28 +94,6 @@
|
||||
'win/crash_other_program.cc',
|
||||
],
|
||||
},
|
||||
{
|
||||
# The handler is only tested on Windows for now.
|
||||
'target_name': 'crashpad_handler_test',
|
||||
'type': 'executable',
|
||||
'dependencies': [
|
||||
'crashpad_handler_test_extended_handler',
|
||||
'handler.gyp:crashpad_handler_lib',
|
||||
'../client/client.gyp:crashpad_client',
|
||||
'../compat/compat.gyp:crashpad_compat',
|
||||
'../test/test.gyp:crashpad_gtest_main',
|
||||
'../test/test.gyp:crashpad_test',
|
||||
'../third_party/gtest/gtest.gyp:gtest',
|
||||
'../third_party/mini_chromium/mini_chromium.gyp:base',
|
||||
'../util/util.gyp:crashpad_util',
|
||||
],
|
||||
'include_dirs': [
|
||||
'..',
|
||||
],
|
||||
'sources': [
|
||||
'crashpad_handler_test.cc',
|
||||
],
|
||||
},
|
||||
{
|
||||
'target_name': 'crashy_program',
|
||||
'type': 'executable',
|
||||
|
193
handler/linux/crash_report_exception_handler.cc
Normal file
193
handler/linux/crash_report_exception_handler.cc
Normal file
@ -0,0 +1,193 @@
|
||||
// Copyright 2018 The Crashpad Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "handler/linux/crash_report_exception_handler.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "client/settings.h"
|
||||
#include "minidump/minidump_file_writer.h"
|
||||
#include "snapshot/crashpad_info_client_options.h"
|
||||
#include "snapshot/linux/process_snapshot_linux.h"
|
||||
#include "snapshot/sanitized/process_snapshot_sanitized.h"
|
||||
#include "snapshot/sanitized/sanitization_information.h"
|
||||
#include "util/linux/direct_ptrace_connection.h"
|
||||
#include "util/linux/ptrace_client.h"
|
||||
#include "util/misc/metrics.h"
|
||||
#include "util/misc/tri_state.h"
|
||||
#include "util/misc/uuid.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
CrashReportExceptionHandler::CrashReportExceptionHandler(
|
||||
CrashReportDatabase* database,
|
||||
CrashReportUploadThread* upload_thread,
|
||||
const std::map<std::string, std::string>* process_annotations,
|
||||
const UserStreamDataSources* user_stream_data_sources)
|
||||
: database_(database),
|
||||
upload_thread_(upload_thread),
|
||||
process_annotations_(process_annotations),
|
||||
user_stream_data_sources_(user_stream_data_sources) {}
|
||||
|
||||
CrashReportExceptionHandler::~CrashReportExceptionHandler() = default;
|
||||
|
||||
bool CrashReportExceptionHandler::HandleException(
|
||||
pid_t client_process_id,
|
||||
const ClientInformation& info) {
|
||||
Metrics::ExceptionEncountered();
|
||||
|
||||
DirectPtraceConnection connection;
|
||||
if (!connection.Initialize(client_process_id)) {
|
||||
Metrics::ExceptionCaptureResult(
|
||||
Metrics::CaptureResult::kDirectPtraceFailed);
|
||||
return false;
|
||||
}
|
||||
|
||||
return HandleExceptionWithConnection(&connection, info);
|
||||
}
|
||||
|
||||
bool CrashReportExceptionHandler::HandleExceptionWithBroker(
|
||||
pid_t client_process_id,
|
||||
const ClientInformation& info,
|
||||
int broker_sock) {
|
||||
Metrics::ExceptionEncountered();
|
||||
|
||||
PtraceClient client;
|
||||
if (!client.Initialize(broker_sock, client_process_id)) {
|
||||
Metrics::ExceptionCaptureResult(
|
||||
Metrics::CaptureResult::kBrokeredPtraceFailed);
|
||||
return false;
|
||||
}
|
||||
|
||||
return HandleExceptionWithConnection(&client, info);
|
||||
}
|
||||
|
||||
bool CrashReportExceptionHandler::HandleExceptionWithConnection(
|
||||
PtraceConnection* connection,
|
||||
const ClientInformation& info) {
|
||||
ProcessSnapshotLinux process_snapshot;
|
||||
if (!process_snapshot.Initialize(connection)) {
|
||||
Metrics::ExceptionCaptureResult(Metrics::CaptureResult::kSnapshotFailed);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!process_snapshot.InitializeException(
|
||||
info.exception_information_address)) {
|
||||
Metrics::ExceptionCaptureResult(
|
||||
Metrics::CaptureResult::kExceptionInitializationFailed);
|
||||
return false;
|
||||
}
|
||||
|
||||
Metrics::ExceptionCode(process_snapshot.Exception()->Exception());
|
||||
|
||||
CrashpadInfoClientOptions client_options;
|
||||
process_snapshot.GetCrashpadOptions(&client_options);
|
||||
if (client_options.crashpad_handler_behavior != TriState::kDisabled) {
|
||||
UUID client_id;
|
||||
Settings* const settings = database_->GetSettings();
|
||||
if (settings) {
|
||||
// If GetSettings() or GetClientID() fails, something else will log a
|
||||
// message and client_id will be left at its default value, all zeroes,
|
||||
// which is appropriate.
|
||||
settings->GetClientID(&client_id);
|
||||
}
|
||||
|
||||
process_snapshot.SetClientID(client_id);
|
||||
process_snapshot.SetAnnotationsSimpleMap(*process_annotations_);
|
||||
|
||||
std::unique_ptr<CrashReportDatabase::NewReport> new_report;
|
||||
CrashReportDatabase::OperationStatus database_status =
|
||||
database_->PrepareNewCrashReport(&new_report);
|
||||
if (database_status != CrashReportDatabase::kNoError) {
|
||||
LOG(ERROR) << "PrepareNewCrashReport failed";
|
||||
Metrics::ExceptionCaptureResult(
|
||||
Metrics::CaptureResult::kPrepareNewCrashReportFailed);
|
||||
return false;
|
||||
}
|
||||
|
||||
process_snapshot.SetReportID(new_report->ReportID());
|
||||
|
||||
ProcessSnapshot* snapshot = nullptr;
|
||||
ProcessSnapshotSanitized sanitized;
|
||||
std::vector<std::string> whitelist;
|
||||
if (info.sanitization_information_address) {
|
||||
SanitizationInformation sanitization_info;
|
||||
ProcessMemoryRange range;
|
||||
if (!range.Initialize(connection->Memory(), connection->Is64Bit()) ||
|
||||
!range.Read(info.sanitization_information_address,
|
||||
sizeof(sanitization_info),
|
||||
&sanitization_info)) {
|
||||
Metrics::ExceptionCaptureResult(
|
||||
Metrics::CaptureResult::kSanitizationInitializationFailed);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sanitization_info.annotations_whitelist_address &&
|
||||
!ReadAnnotationsWhitelist(
|
||||
range,
|
||||
sanitization_info.annotations_whitelist_address,
|
||||
&whitelist)) {
|
||||
Metrics::ExceptionCaptureResult(
|
||||
Metrics::CaptureResult::kSanitizationInitializationFailed);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!sanitized.Initialize(&process_snapshot,
|
||||
sanitization_info.annotations_whitelist_address
|
||||
? &whitelist
|
||||
: nullptr,
|
||||
sanitization_info.target_module_address,
|
||||
sanitization_info.sanitize_stacks)) {
|
||||
Metrics::ExceptionCaptureResult(
|
||||
Metrics::CaptureResult::kSkippedDueToSanitization);
|
||||
return true;
|
||||
}
|
||||
|
||||
snapshot = &sanitized;
|
||||
} else {
|
||||
snapshot = &process_snapshot;
|
||||
}
|
||||
|
||||
MinidumpFileWriter minidump;
|
||||
minidump.InitializeFromSnapshot(snapshot);
|
||||
AddUserExtensionStreams(user_stream_data_sources_, snapshot, &minidump);
|
||||
|
||||
if (!minidump.WriteEverything(new_report->Writer())) {
|
||||
LOG(ERROR) << "WriteEverything failed";
|
||||
Metrics::ExceptionCaptureResult(
|
||||
Metrics::CaptureResult::kMinidumpWriteFailed);
|
||||
return false;
|
||||
}
|
||||
|
||||
UUID uuid;
|
||||
database_status =
|
||||
database_->FinishedWritingCrashReport(std::move(new_report), &uuid);
|
||||
if (database_status != CrashReportDatabase::kNoError) {
|
||||
LOG(ERROR) << "FinishedWritingCrashReport failed";
|
||||
Metrics::ExceptionCaptureResult(
|
||||
Metrics::CaptureResult::kFinishedWritingCrashReportFailed);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (upload_thread_) {
|
||||
upload_thread_->ReportPending(uuid);
|
||||
}
|
||||
}
|
||||
|
||||
Metrics::ExceptionCaptureResult(Metrics::CaptureResult::kSuccess);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace crashpad
|
87
handler/linux/crash_report_exception_handler.h
Normal file
87
handler/linux/crash_report_exception_handler.h
Normal file
@ -0,0 +1,87 @@
|
||||
// Copyright 2018 The Crashpad Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef CRASHPAD_HANDLER_LINUX_CRASH_REPORT_EXCEPTION_HANDLER_H_
|
||||
#define CRASHPAD_HANDLER_LINUX_CRASH_REPORT_EXCEPTION_HANDLER_H_
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "base/macros.h"
|
||||
#include "client/crash_report_database.h"
|
||||
#include "handler/crash_report_upload_thread.h"
|
||||
#include "handler/linux/exception_handler_server.h"
|
||||
#include "handler/user_stream_data_source.h"
|
||||
#include "util/linux/exception_handler_protocol.h"
|
||||
#include "util/linux/ptrace_connection.h"
|
||||
#include "util/misc/address_types.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
//! \brief An exception handler that writes crash reports for exceptions
|
||||
//! to a CrashReportDatabase.
|
||||
class CrashReportExceptionHandler : public ExceptionHandlerServer::Delegate {
|
||||
public:
|
||||
//! \brief Creates a new object that will store crash reports in \a database.
|
||||
//!
|
||||
//! \param[in] database The database to store crash reports in. Weak.
|
||||
//! \param[in] upload_thread The upload thread to notify when a new crash
|
||||
//! report is written into \a database. Report upload is skipped if this
|
||||
//! value is `nullptr`.
|
||||
//! \param[in] process_annotations A map of annotations to insert as
|
||||
//! process-level annotations into each crash report that is written. Do
|
||||
//! not confuse this with module-level annotations, which are under the
|
||||
//! control of the crashing process, and are used to implement Chrome’s
|
||||
//! “crash keys.” Process-level annotations are those that are beyond the
|
||||
//! control of the crashing process, which must reliably be set even if
|
||||
//! the process crashes before it’s able to establish its own annotations.
|
||||
//! To interoperate with Breakpad servers, the recommended practice is to
|
||||
//! specify values for the `"prod"` and `"ver"` keys as process
|
||||
//! annotations.
|
||||
//! \param[in] user_stream_data_sources Data sources to be used to extend
|
||||
//! crash reports. For each crash report that is written, the data sources
|
||||
//! are called in turn. These data sources may contribute additional
|
||||
//! minidump streams. `nullptr` if not required.
|
||||
CrashReportExceptionHandler(
|
||||
CrashReportDatabase* database,
|
||||
CrashReportUploadThread* upload_thread,
|
||||
const std::map<std::string, std::string>* process_annotations,
|
||||
const UserStreamDataSources* user_stream_data_sources);
|
||||
|
||||
~CrashReportExceptionHandler();
|
||||
|
||||
// ExceptionHandlerServer::Delegate:
|
||||
|
||||
bool HandleException(pid_t client_process_id,
|
||||
const ClientInformation& info) override;
|
||||
|
||||
bool HandleExceptionWithBroker(pid_t client_process_id,
|
||||
const ClientInformation& info,
|
||||
int broker_sock) override;
|
||||
|
||||
private:
|
||||
bool HandleExceptionWithConnection(PtraceConnection* connection,
|
||||
const ClientInformation& info);
|
||||
|
||||
CrashReportDatabase* database_; // weak
|
||||
CrashReportUploadThread* upload_thread_; // weak
|
||||
const std::map<std::string, std::string>* process_annotations_; // weak
|
||||
const UserStreamDataSources* user_stream_data_sources_; // weak
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(CrashReportExceptionHandler);
|
||||
};
|
||||
|
||||
} // namespace crashpad
|
||||
|
||||
#endif // CRASHPAD_HANDLER_LINUX_CRASH_REPORT_EXCEPTION_HANDLER_H_
|
462
handler/linux/exception_handler_server.cc
Normal file
462
handler/linux/exception_handler_server.cc
Normal file
@ -0,0 +1,462 @@
|
||||
// Copyright 2017 The Crashpad Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "handler/linux/exception_handler_server.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <sys/capability.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <sys/eventfd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "base/compiler_specific.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/posix/eintr_wrapper.h"
|
||||
#include "base/strings/string_number_conversions.h"
|
||||
#include "build/build_config.h"
|
||||
#include "util/file/file_io.h"
|
||||
#include "util/file/filesystem.h"
|
||||
#include "util/misc/as_underlying_type.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
namespace {
|
||||
|
||||
// Log an error for a socket after an EPOLLERR.
|
||||
void LogSocketError(int sock) {
|
||||
int err;
|
||||
socklen_t err_len = sizeof(err);
|
||||
if (getsockopt(sock, SOL_SOCKET, SO_ERROR, &err, &err_len) != 0) {
|
||||
PLOG(ERROR) << "getsockopt";
|
||||
} else {
|
||||
errno = err;
|
||||
PLOG(ERROR) << "EPOLLERR";
|
||||
}
|
||||
}
|
||||
|
||||
enum class PtraceScope {
|
||||
kClassic = 0,
|
||||
kRestricted,
|
||||
kAdminOnly,
|
||||
kNoAttach,
|
||||
kUnknown
|
||||
};
|
||||
|
||||
PtraceScope GetPtraceScope() {
|
||||
const base::FilePath settings_file("/proc/sys/kernel/yama/ptrace_scope");
|
||||
if (!IsRegularFile(base::FilePath(settings_file))) {
|
||||
return PtraceScope::kClassic;
|
||||
}
|
||||
|
||||
std::string contents;
|
||||
if (!LoggingReadEntireFile(settings_file, &contents)) {
|
||||
return PtraceScope::kUnknown;
|
||||
}
|
||||
|
||||
if (contents.back() != '\n') {
|
||||
LOG(ERROR) << "format error";
|
||||
return PtraceScope::kUnknown;
|
||||
}
|
||||
contents.pop_back();
|
||||
|
||||
int ptrace_scope;
|
||||
if (!base::StringToInt(contents, &ptrace_scope)) {
|
||||
LOG(ERROR) << "format error";
|
||||
return PtraceScope::kUnknown;
|
||||
}
|
||||
|
||||
if (ptrace_scope < static_cast<int>(PtraceScope::kClassic) ||
|
||||
ptrace_scope >= static_cast<int>(PtraceScope::kUnknown)) {
|
||||
LOG(ERROR) << "invalid ptrace scope";
|
||||
return PtraceScope::kUnknown;
|
||||
}
|
||||
|
||||
return static_cast<PtraceScope>(ptrace_scope);
|
||||
}
|
||||
|
||||
bool HaveCapSysPtrace() {
|
||||
struct __user_cap_header_struct cap_header = {};
|
||||
struct __user_cap_data_struct cap_data = {};
|
||||
|
||||
cap_header.pid = getpid();
|
||||
|
||||
if (capget(&cap_header, &cap_data) != 0) {
|
||||
PLOG(ERROR) << "capget";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cap_header.version != _LINUX_CAPABILITY_VERSION_3) {
|
||||
LOG(ERROR) << "Unexpected capability version " << std::hex
|
||||
<< cap_header.version;
|
||||
return false;
|
||||
}
|
||||
|
||||
return (cap_data.effective & (1 << CAP_SYS_PTRACE)) != 0;
|
||||
}
|
||||
|
||||
bool SendMessageToClient(int client_sock, ServerToClientMessage::Type type) {
|
||||
ServerToClientMessage message = {};
|
||||
message.type = type;
|
||||
if (type == ServerToClientMessage::kTypeSetPtracer) {
|
||||
message.pid = getpid();
|
||||
}
|
||||
return LoggingWriteFile(client_sock, &message, sizeof(message));
|
||||
}
|
||||
|
||||
class PtraceStrategyDeciderImpl : public PtraceStrategyDecider {
|
||||
public:
|
||||
PtraceStrategyDeciderImpl() : PtraceStrategyDecider() {}
|
||||
~PtraceStrategyDeciderImpl() = default;
|
||||
|
||||
Strategy ChooseStrategy(int sock, const ucred& client_credentials) override {
|
||||
switch (GetPtraceScope()) {
|
||||
case PtraceScope::kClassic:
|
||||
if (getuid() == client_credentials.uid) {
|
||||
return Strategy::kDirectPtrace;
|
||||
}
|
||||
return TryForkingBroker(sock);
|
||||
|
||||
case PtraceScope::kRestricted:
|
||||
if (!SendMessageToClient(sock,
|
||||
ServerToClientMessage::kTypeSetPtracer)) {
|
||||
return Strategy::kError;
|
||||
}
|
||||
|
||||
Errno status;
|
||||
if (!LoggingReadFileExactly(sock, &status, sizeof(status))) {
|
||||
return Strategy::kError;
|
||||
}
|
||||
|
||||
if (status != 0) {
|
||||
errno = status;
|
||||
PLOG(ERROR) << "Handler Client SetPtracer";
|
||||
return TryForkingBroker(sock);
|
||||
}
|
||||
return Strategy::kDirectPtrace;
|
||||
|
||||
case PtraceScope::kAdminOnly:
|
||||
if (HaveCapSysPtrace()) {
|
||||
return Strategy::kDirectPtrace;
|
||||
}
|
||||
FALLTHROUGH;
|
||||
case PtraceScope::kNoAttach:
|
||||
LOG(WARNING) << "no ptrace";
|
||||
return Strategy::kNoPtrace;
|
||||
|
||||
case PtraceScope::kUnknown:
|
||||
LOG(WARNING) << "Unknown ptrace scope";
|
||||
return Strategy::kError;
|
||||
}
|
||||
|
||||
DCHECK(false);
|
||||
return Strategy::kError;
|
||||
}
|
||||
|
||||
private:
|
||||
static Strategy TryForkingBroker(int client_sock) {
|
||||
if (!SendMessageToClient(client_sock,
|
||||
ServerToClientMessage::kTypeForkBroker)) {
|
||||
return Strategy::kError;
|
||||
}
|
||||
|
||||
Errno status;
|
||||
if (!LoggingReadFileExactly(client_sock, &status, sizeof(status))) {
|
||||
return Strategy::kError;
|
||||
}
|
||||
|
||||
if (status != 0) {
|
||||
errno = status;
|
||||
PLOG(ERROR) << "Handler Client ForkBroker";
|
||||
return Strategy::kNoPtrace;
|
||||
}
|
||||
return Strategy::kUseBroker;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
struct ExceptionHandlerServer::Event {
|
||||
enum class Type { kShutdown, kClientMessage } type;
|
||||
|
||||
ScopedFileHandle fd;
|
||||
};
|
||||
|
||||
ExceptionHandlerServer::ExceptionHandlerServer()
|
||||
: clients_(),
|
||||
shutdown_event_(),
|
||||
strategy_decider_(new PtraceStrategyDeciderImpl()),
|
||||
delegate_(nullptr),
|
||||
pollfd_(),
|
||||
keep_running_(true) {}
|
||||
|
||||
ExceptionHandlerServer::~ExceptionHandlerServer() = default;
|
||||
|
||||
void ExceptionHandlerServer::SetPtraceStrategyDecider(
|
||||
std::unique_ptr<PtraceStrategyDecider> decider) {
|
||||
strategy_decider_ = std::move(decider);
|
||||
}
|
||||
|
||||
bool ExceptionHandlerServer::InitializeWithClient(ScopedFileHandle sock) {
|
||||
INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
|
||||
|
||||
pollfd_.reset(epoll_create1(EPOLL_CLOEXEC));
|
||||
if (!pollfd_.is_valid()) {
|
||||
PLOG(ERROR) << "epoll_create1";
|
||||
return false;
|
||||
}
|
||||
|
||||
shutdown_event_ = std::make_unique<Event>();
|
||||
shutdown_event_->type = Event::Type::kShutdown;
|
||||
shutdown_event_->fd.reset(eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK));
|
||||
if (!shutdown_event_->fd.is_valid()) {
|
||||
PLOG(ERROR) << "eventfd";
|
||||
return false;
|
||||
}
|
||||
|
||||
epoll_event poll_event;
|
||||
poll_event.events = EPOLLIN;
|
||||
poll_event.data.ptr = shutdown_event_.get();
|
||||
if (epoll_ctl(pollfd_.get(),
|
||||
EPOLL_CTL_ADD,
|
||||
shutdown_event_->fd.get(),
|
||||
&poll_event) != 0) {
|
||||
PLOG(ERROR) << "epoll_ctl";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!InstallClientSocket(std::move(sock))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
INITIALIZATION_STATE_SET_VALID(initialized_);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ExceptionHandlerServer::Run(Delegate* delegate) {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
delegate_ = delegate;
|
||||
|
||||
while (keep_running_ && clients_.size() > 0) {
|
||||
epoll_event poll_event;
|
||||
int res = HANDLE_EINTR(epoll_wait(pollfd_.get(), &poll_event, 1, -1));
|
||||
if (res < 0) {
|
||||
PLOG(ERROR) << "epoll_wait";
|
||||
return;
|
||||
}
|
||||
DCHECK_EQ(res, 1);
|
||||
|
||||
Event* eventp = reinterpret_cast<Event*>(poll_event.data.ptr);
|
||||
if (eventp->type == Event::Type::kShutdown) {
|
||||
if (poll_event.events & EPOLLERR) {
|
||||
LogSocketError(eventp->fd.get());
|
||||
}
|
||||
keep_running_ = false;
|
||||
} else {
|
||||
HandleEvent(eventp, poll_event.events);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ExceptionHandlerServer::Stop() {
|
||||
keep_running_ = false;
|
||||
if (shutdown_event_ && shutdown_event_->fd.is_valid()) {
|
||||
uint64_t value = 1;
|
||||
LoggingWriteFile(shutdown_event_->fd.get(), &value, sizeof(value));
|
||||
}
|
||||
}
|
||||
|
||||
void ExceptionHandlerServer::HandleEvent(Event* event, uint32_t event_type) {
|
||||
DCHECK_EQ(AsUnderlyingType(event->type),
|
||||
AsUnderlyingType(Event::Type::kClientMessage));
|
||||
|
||||
if (event_type & EPOLLERR) {
|
||||
LogSocketError(event->fd.get());
|
||||
UninstallClientSocket(event);
|
||||
return;
|
||||
}
|
||||
|
||||
if (event_type & EPOLLIN) {
|
||||
if (!ReceiveClientMessage(event)) {
|
||||
UninstallClientSocket(event);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (event_type & EPOLLHUP || event_type & EPOLLRDHUP) {
|
||||
UninstallClientSocket(event);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG(ERROR) << "Unexpected event 0x" << std::hex << event_type;
|
||||
return;
|
||||
}
|
||||
|
||||
bool ExceptionHandlerServer::InstallClientSocket(ScopedFileHandle socket) {
|
||||
int optval = 1;
|
||||
socklen_t optlen = sizeof(optval);
|
||||
if (setsockopt(socket.get(), SOL_SOCKET, SO_PASSCRED, &optval, optlen) != 0) {
|
||||
PLOG(ERROR) << "setsockopt";
|
||||
return false;
|
||||
}
|
||||
|
||||
auto event = std::make_unique<Event>();
|
||||
event->type = Event::Type::kClientMessage;
|
||||
event->fd.reset(socket.release());
|
||||
|
||||
Event* eventp = event.get();
|
||||
|
||||
if (!clients_.insert(std::make_pair(event->fd.get(), std::move(event)))
|
||||
.second) {
|
||||
LOG(ERROR) << "duplicate descriptor";
|
||||
return false;
|
||||
}
|
||||
|
||||
epoll_event poll_event;
|
||||
poll_event.events = EPOLLIN | EPOLLRDHUP;
|
||||
poll_event.data.ptr = eventp;
|
||||
|
||||
if (epoll_ctl(pollfd_.get(), EPOLL_CTL_ADD, eventp->fd.get(), &poll_event) !=
|
||||
0) {
|
||||
PLOG(ERROR) << "epoll_ctl";
|
||||
clients_.erase(eventp->fd.get());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ExceptionHandlerServer::UninstallClientSocket(Event* event) {
|
||||
if (epoll_ctl(pollfd_.get(), EPOLL_CTL_DEL, event->fd.get(), nullptr) != 0) {
|
||||
PLOG(ERROR) << "epoll_ctl";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (clients_.erase(event->fd.get()) != 1) {
|
||||
LOG(ERROR) << "event not found";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ExceptionHandlerServer::ReceiveClientMessage(Event* event) {
|
||||
ClientToServerMessage message;
|
||||
iovec iov;
|
||||
iov.iov_base = &message;
|
||||
iov.iov_len = sizeof(message);
|
||||
|
||||
msghdr msg;
|
||||
msg.msg_name = nullptr;
|
||||
msg.msg_namelen = 0;
|
||||
msg.msg_iov = &iov;
|
||||
msg.msg_iovlen = 1;
|
||||
|
||||
char cmsg_buf[CMSG_SPACE(sizeof(ucred))];
|
||||
msg.msg_control = cmsg_buf;
|
||||
msg.msg_controllen = sizeof(cmsg_buf);
|
||||
msg.msg_flags = 0;
|
||||
|
||||
int res = HANDLE_EINTR(recvmsg(event->fd.get(), &msg, 0));
|
||||
if (res < 0) {
|
||||
PLOG(ERROR) << "recvmsg";
|
||||
return false;
|
||||
}
|
||||
if (res == 0) {
|
||||
// The client had an orderly shutdown.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (msg.msg_name != nullptr || msg.msg_namelen != 0) {
|
||||
LOG(ERROR) << "unexpected msg name";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (msg.msg_iovlen != 1) {
|
||||
LOG(ERROR) << "unexpected iovlen";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (msg.msg_iov[0].iov_len != sizeof(ClientToServerMessage)) {
|
||||
LOG(ERROR) << "unexpected message size " << msg.msg_iov[0].iov_len;
|
||||
return false;
|
||||
}
|
||||
auto client_msg =
|
||||
reinterpret_cast<ClientToServerMessage*>(msg.msg_iov[0].iov_base);
|
||||
|
||||
switch (client_msg->type) {
|
||||
case ClientToServerMessage::kCrashDumpRequest:
|
||||
return HandleCrashDumpRequest(
|
||||
msg, client_msg->client_info, event->fd.get());
|
||||
}
|
||||
|
||||
DCHECK(false);
|
||||
LOG(ERROR) << "Unknown message type";
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ExceptionHandlerServer::HandleCrashDumpRequest(
|
||||
const msghdr& msg,
|
||||
const ClientInformation& client_info,
|
||||
int client_sock) {
|
||||
cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
|
||||
if (cmsg == nullptr) {
|
||||
LOG(ERROR) << "missing credentials";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cmsg->cmsg_level != SOL_SOCKET) {
|
||||
LOG(ERROR) << "unexpected cmsg_level " << cmsg->cmsg_level;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cmsg->cmsg_type != SCM_CREDENTIALS) {
|
||||
LOG(ERROR) << "unexpected cmsg_type " << cmsg->cmsg_type;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cmsg->cmsg_len != CMSG_LEN(sizeof(ucred))) {
|
||||
LOG(ERROR) << "unexpected cmsg_len " << cmsg->cmsg_len;
|
||||
return false;
|
||||
}
|
||||
|
||||
ucred* client_credentials = reinterpret_cast<ucred*>(CMSG_DATA(cmsg));
|
||||
pid_t client_process_id = client_credentials->pid;
|
||||
|
||||
switch (strategy_decider_->ChooseStrategy(client_sock, *client_credentials)) {
|
||||
case PtraceStrategyDecider::Strategy::kError:
|
||||
return false;
|
||||
|
||||
case PtraceStrategyDecider::Strategy::kNoPtrace:
|
||||
return SendMessageToClient(client_sock,
|
||||
ServerToClientMessage::kTypeCrashDumpFailed);
|
||||
|
||||
case PtraceStrategyDecider::Strategy::kDirectPtrace:
|
||||
delegate_->HandleException(client_process_id, client_info);
|
||||
break;
|
||||
|
||||
case PtraceStrategyDecider::Strategy::kUseBroker:
|
||||
delegate_->HandleExceptionWithBroker(
|
||||
client_process_id, client_info, client_sock);
|
||||
break;
|
||||
}
|
||||
|
||||
return SendMessageToClient(client_sock,
|
||||
ServerToClientMessage::kTypeCrashDumpComplete);
|
||||
}
|
||||
|
||||
} // namespace crashpad
|
152
handler/linux/exception_handler_server.h
Normal file
152
handler/linux/exception_handler_server.h
Normal file
@ -0,0 +1,152 @@
|
||||
// Copyright 2017 The Crashpad Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef CRASHPAD_HANDLER_LINUX_EXCEPTION_HANDLER_SERVER_H_
|
||||
#define CRASHPAD_HANDLER_LINUX_EXCEPTION_HANDLER_SERVER_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "base/macros.h"
|
||||
#include "util/file/file_io.h"
|
||||
#include "util/linux/exception_handler_protocol.h"
|
||||
#include "util/misc/address_types.h"
|
||||
#include "util/misc/initialization_state_dcheck.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
//! \brief Abstract base class for deciding how the handler should `ptrace` a
|
||||
//! client.
|
||||
class PtraceStrategyDecider {
|
||||
public:
|
||||
virtual ~PtraceStrategyDecider() = default;
|
||||
|
||||
//! \brief The possible return values for ChooseStrategy().
|
||||
enum class Strategy {
|
||||
//! \brief An error occurred, with a message logged.
|
||||
kError,
|
||||
|
||||
//! \brief Ptrace cannot be used.
|
||||
kNoPtrace,
|
||||
|
||||
//! \brief The handler should `ptrace`-attach the client directly.
|
||||
kDirectPtrace,
|
||||
|
||||
//! \brief The client has `fork`ed a PtraceBroker for the handler.
|
||||
kUseBroker,
|
||||
};
|
||||
|
||||
//! \brief Chooses an appropriate `ptrace` strategy.
|
||||
//!
|
||||
//! \param[in] sock A socket conncted to a ExceptionHandlerClient.
|
||||
//! \param[in] client_credentials The credentials for the connected client.
|
||||
//! \return the chosen #Strategy.
|
||||
virtual Strategy ChooseStrategy(int sock,
|
||||
const ucred& client_credentials) = 0;
|
||||
|
||||
protected:
|
||||
PtraceStrategyDecider() = default;
|
||||
};
|
||||
|
||||
//! \brief Runs the main exception-handling server in Crashpad’s handler
|
||||
//! process.
|
||||
class ExceptionHandlerServer {
|
||||
public:
|
||||
class Delegate {
|
||||
public:
|
||||
//! \brief Called on receipt of a crash dump request from a client.
|
||||
//!
|
||||
//! \param[in] client_process_id The process ID of the crashing client.
|
||||
//! \param[in] info Information on the client.
|
||||
//! \return `true` on success. `false` on failure with a message logged.
|
||||
virtual bool HandleException(pid_t client_process_id,
|
||||
const ClientInformation& info) = 0;
|
||||
|
||||
//! \brief Called on the receipt of a crash dump request from a client for a
|
||||
//! crash that should be mediated by a PtraceBroker.
|
||||
//!
|
||||
//! \param[in] client_process_id The process ID of the crashing client.
|
||||
//! \param[in] info Information on the client.
|
||||
//! \param[in] broker_sock A socket connected to the PtraceBroker.
|
||||
//! \return `true` on success. `false` on failure with a message logged.
|
||||
virtual bool HandleExceptionWithBroker(pid_t client_process_id,
|
||||
const ClientInformation& info,
|
||||
int broker_sock) = 0;
|
||||
|
||||
protected:
|
||||
~Delegate() {}
|
||||
};
|
||||
|
||||
ExceptionHandlerServer();
|
||||
~ExceptionHandlerServer();
|
||||
|
||||
//! \brief Sets the handler's PtraceStrategyDecider.
|
||||
//!
|
||||
//! If this method is not called, a default PtraceStrategyDecider will be
|
||||
//! used.
|
||||
void SetPtraceStrategyDecider(std::unique_ptr<PtraceStrategyDecider> decider);
|
||||
|
||||
//! \brief Initializes this object.
|
||||
//!
|
||||
//! This method must be successfully called before Run().
|
||||
//!
|
||||
//! \param[in] sock A socket on which to receive client requests.
|
||||
//! \return `true` on success. `false` on failure with a message logged.
|
||||
bool InitializeWithClient(ScopedFileHandle sock);
|
||||
|
||||
//! \brief Runs the exception-handling server.
|
||||
//!
|
||||
//! This method must only be called once on an ExceptionHandlerServer object.
|
||||
//! This method returns when there are no more client connections or Stop()
|
||||
//! has been called.
|
||||
//!
|
||||
//! \param[in] delegate An object to send exceptions to.
|
||||
void Run(Delegate* delegate);
|
||||
|
||||
//! \brief Stops a running exception-handling server.
|
||||
//!
|
||||
//! Stop() may be called at any time, and may be called from a signal handler.
|
||||
//! If Stop() is called before Run() it will cause Run() to return as soon as
|
||||
//! it is called. It is harmless to call Stop() after Run() has already
|
||||
//! returned, or to call Stop() after it has already been called.
|
||||
void Stop();
|
||||
|
||||
private:
|
||||
struct Event;
|
||||
|
||||
void HandleEvent(Event* event, uint32_t event_type);
|
||||
bool InstallClientSocket(ScopedFileHandle socket);
|
||||
bool UninstallClientSocket(Event* event);
|
||||
bool ReceiveClientMessage(Event* event);
|
||||
bool HandleCrashDumpRequest(const msghdr& msg,
|
||||
const ClientInformation& client_info,
|
||||
int client_sock);
|
||||
|
||||
std::unordered_map<int, std::unique_ptr<Event>> clients_;
|
||||
std::unique_ptr<Event> shutdown_event_;
|
||||
std::unique_ptr<PtraceStrategyDecider> strategy_decider_;
|
||||
Delegate* delegate_;
|
||||
ScopedFileHandle pollfd_;
|
||||
bool keep_running_;
|
||||
InitializationStateDcheck initialized_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ExceptionHandlerServer);
|
||||
};
|
||||
|
||||
} // namespace crashpad
|
||||
|
||||
#endif // CRASHPAD_HANDLER_LINUX_EXCEPTION_HANDLER_SERVER_H_
|
326
handler/linux/exception_handler_server_test.cc
Normal file
326
handler/linux/exception_handler_server_test.cc
Normal file
@ -0,0 +1,326 @@
|
||||
// Copyright 2017 The Crashpad Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "handler/linux/exception_handler_server.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "test/errors.h"
|
||||
#include "test/multiprocess.h"
|
||||
#include "util/linux/direct_ptrace_connection.h"
|
||||
#include "util/linux/exception_handler_client.h"
|
||||
#include "util/linux/ptrace_client.h"
|
||||
#include "util/linux/scoped_pr_set_ptracer.h"
|
||||
#include "util/synchronization/semaphore.h"
|
||||
#include "util/thread/thread.h"
|
||||
|
||||
namespace crashpad {
|
||||
namespace test {
|
||||
namespace {
|
||||
|
||||
// Runs the ExceptionHandlerServer on a background thread.
|
||||
class RunServerThread : public Thread {
|
||||
public:
|
||||
RunServerThread(ExceptionHandlerServer* server,
|
||||
ExceptionHandlerServer::Delegate* delegate)
|
||||
: server_(server), delegate_(delegate), join_sem_(0) {}
|
||||
|
||||
~RunServerThread() override {}
|
||||
|
||||
bool JoinWithTimeout(double timeout) {
|
||||
if (!join_sem_.TimedWait(timeout)) {
|
||||
return false;
|
||||
}
|
||||
Join();
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
// Thread:
|
||||
void ThreadMain() override {
|
||||
server_->Run(delegate_);
|
||||
join_sem_.Signal();
|
||||
}
|
||||
|
||||
ExceptionHandlerServer* server_;
|
||||
ExceptionHandlerServer::Delegate* delegate_;
|
||||
Semaphore join_sem_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(RunServerThread);
|
||||
};
|
||||
|
||||
class ScopedStopServerAndJoinThread {
|
||||
public:
|
||||
ScopedStopServerAndJoinThread(ExceptionHandlerServer* server,
|
||||
RunServerThread* thread)
|
||||
: server_(server), thread_(thread) {}
|
||||
|
||||
~ScopedStopServerAndJoinThread() {
|
||||
server_->Stop();
|
||||
EXPECT_TRUE(thread_->JoinWithTimeout(5.0));
|
||||
}
|
||||
|
||||
private:
|
||||
ExceptionHandlerServer* server_;
|
||||
RunServerThread* thread_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ScopedStopServerAndJoinThread);
|
||||
};
|
||||
|
||||
class TestDelegate : public ExceptionHandlerServer::Delegate {
|
||||
public:
|
||||
TestDelegate()
|
||||
: Delegate(), last_exception_address_(0), last_client_(-1), sem_(0) {}
|
||||
|
||||
~TestDelegate() {}
|
||||
|
||||
bool WaitForException(double timeout_seconds,
|
||||
pid_t* last_client,
|
||||
VMAddress* last_address) {
|
||||
if (sem_.TimedWait(timeout_seconds)) {
|
||||
*last_client = last_client_;
|
||||
*last_address = last_exception_address_;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HandleException(pid_t client_process_id,
|
||||
const ClientInformation& info) override {
|
||||
DirectPtraceConnection connection;
|
||||
bool connected = connection.Initialize(client_process_id);
|
||||
EXPECT_TRUE(connected);
|
||||
|
||||
last_exception_address_ = info.exception_information_address;
|
||||
last_client_ = client_process_id;
|
||||
sem_.Signal();
|
||||
return connected;
|
||||
}
|
||||
|
||||
bool HandleExceptionWithBroker(pid_t client_process_id,
|
||||
const ClientInformation& info,
|
||||
int broker_sock) override {
|
||||
PtraceClient client;
|
||||
bool connected = client.Initialize(broker_sock, client_process_id);
|
||||
EXPECT_TRUE(connected);
|
||||
|
||||
last_exception_address_ = info.exception_information_address,
|
||||
last_client_ = client_process_id;
|
||||
sem_.Signal();
|
||||
return connected;
|
||||
}
|
||||
|
||||
private:
|
||||
VMAddress last_exception_address_;
|
||||
pid_t last_client_;
|
||||
Semaphore sem_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(TestDelegate);
|
||||
};
|
||||
|
||||
class MockPtraceStrategyDecider : public PtraceStrategyDecider {
|
||||
public:
|
||||
MockPtraceStrategyDecider(PtraceStrategyDecider::Strategy strategy)
|
||||
: PtraceStrategyDecider(), strategy_(strategy) {}
|
||||
|
||||
~MockPtraceStrategyDecider() {}
|
||||
|
||||
Strategy ChooseStrategy(int sock, const ucred& client_credentials) override {
|
||||
if (strategy_ == Strategy::kUseBroker) {
|
||||
ServerToClientMessage message = {};
|
||||
message.type = ServerToClientMessage::kTypeForkBroker;
|
||||
|
||||
Errno status;
|
||||
bool result = LoggingWriteFile(sock, &message, sizeof(message)) &&
|
||||
LoggingReadFileExactly(sock, &status, sizeof(status));
|
||||
EXPECT_TRUE(result);
|
||||
|
||||
if (!result) {
|
||||
return Strategy::kError;
|
||||
}
|
||||
|
||||
if (status != 0) {
|
||||
errno = status;
|
||||
ADD_FAILURE() << ErrnoMessage("Handler Client ForkBroker");
|
||||
return Strategy::kNoPtrace;
|
||||
}
|
||||
}
|
||||
return strategy_;
|
||||
}
|
||||
|
||||
private:
|
||||
Strategy strategy_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(MockPtraceStrategyDecider);
|
||||
};
|
||||
|
||||
class ExceptionHandlerServerTest : public testing::Test {
|
||||
public:
|
||||
ExceptionHandlerServerTest()
|
||||
: server_(),
|
||||
delegate_(),
|
||||
server_thread_(&server_, &delegate_),
|
||||
sock_to_handler_() {}
|
||||
|
||||
~ExceptionHandlerServerTest() = default;
|
||||
|
||||
int SockToHandler() { return sock_to_handler_.get(); }
|
||||
|
||||
TestDelegate* Delegate() { return &delegate_; }
|
||||
|
||||
void Hangup() { sock_to_handler_.reset(); }
|
||||
|
||||
RunServerThread* ServerThread() { return &server_thread_; }
|
||||
|
||||
ExceptionHandlerServer* Server() { return &server_; }
|
||||
|
||||
class CrashDumpTest : public Multiprocess {
|
||||
public:
|
||||
CrashDumpTest(ExceptionHandlerServerTest* server_test, bool succeeds)
|
||||
: Multiprocess(), server_test_(server_test), succeeds_(succeeds) {}
|
||||
|
||||
~CrashDumpTest() = default;
|
||||
|
||||
void MultiprocessParent() override {
|
||||
ClientInformation info;
|
||||
ASSERT_TRUE(
|
||||
LoggingReadFileExactly(ReadPipeHandle(), &info, sizeof(info)));
|
||||
|
||||
if (succeeds_) {
|
||||
VMAddress last_address;
|
||||
pid_t last_client;
|
||||
ASSERT_TRUE(server_test_->Delegate()->WaitForException(
|
||||
5.0, &last_client, &last_address));
|
||||
EXPECT_EQ(last_address, info.exception_information_address);
|
||||
EXPECT_EQ(last_client, ChildPID());
|
||||
} else {
|
||||
CheckedReadFileAtEOF(ReadPipeHandle());
|
||||
}
|
||||
}
|
||||
|
||||
void MultiprocessChild() override {
|
||||
ASSERT_EQ(close(server_test_->sock_to_client_), 0);
|
||||
|
||||
ClientInformation info;
|
||||
info.exception_information_address = 42;
|
||||
|
||||
ASSERT_TRUE(LoggingWriteFile(WritePipeHandle(), &info, sizeof(info)));
|
||||
|
||||
// If the current ptrace_scope is restricted, the broker needs to be set
|
||||
// as the ptracer for this process. Setting this process as its own
|
||||
// ptracer allows the broker to inherit this condition.
|
||||
ScopedPrSetPtracer set_ptracer(getpid(), /* may_log= */ true);
|
||||
|
||||
ExceptionHandlerClient client(server_test_->SockToHandler());
|
||||
ASSERT_EQ(client.RequestCrashDump(info), 0);
|
||||
}
|
||||
|
||||
private:
|
||||
ExceptionHandlerServerTest* server_test_;
|
||||
bool succeeds_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(CrashDumpTest);
|
||||
};
|
||||
|
||||
void ExpectCrashDumpUsingStrategy(PtraceStrategyDecider::Strategy strategy,
|
||||
bool succeeds) {
|
||||
ScopedStopServerAndJoinThread stop_server(Server(), ServerThread());
|
||||
ServerThread()->Start();
|
||||
|
||||
Server()->SetPtraceStrategyDecider(
|
||||
std::make_unique<MockPtraceStrategyDecider>(strategy));
|
||||
|
||||
CrashDumpTest test(this, succeeds);
|
||||
test.Run();
|
||||
}
|
||||
|
||||
protected:
|
||||
void SetUp() override {
|
||||
int socks[2];
|
||||
ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, socks), 0);
|
||||
sock_to_handler_.reset(socks[0]);
|
||||
sock_to_client_ = socks[1];
|
||||
|
||||
ASSERT_TRUE(server_.InitializeWithClient(ScopedFileHandle(socks[1])));
|
||||
}
|
||||
|
||||
private:
|
||||
ExceptionHandlerServer server_;
|
||||
TestDelegate delegate_;
|
||||
RunServerThread server_thread_;
|
||||
ScopedFileHandle sock_to_handler_;
|
||||
int sock_to_client_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ExceptionHandlerServerTest);
|
||||
};
|
||||
|
||||
TEST_F(ExceptionHandlerServerTest, ShutdownWithNoClients) {
|
||||
ServerThread()->Start();
|
||||
Hangup();
|
||||
ASSERT_TRUE(ServerThread()->JoinWithTimeout(5.0));
|
||||
}
|
||||
|
||||
TEST_F(ExceptionHandlerServerTest, StopWithClients) {
|
||||
ServerThread()->Start();
|
||||
Server()->Stop();
|
||||
ASSERT_TRUE(ServerThread()->JoinWithTimeout(5.0));
|
||||
}
|
||||
|
||||
TEST_F(ExceptionHandlerServerTest, StopBeforeRun) {
|
||||
Server()->Stop();
|
||||
ServerThread()->Start();
|
||||
ASSERT_TRUE(ServerThread()->JoinWithTimeout(5.0));
|
||||
}
|
||||
|
||||
TEST_F(ExceptionHandlerServerTest, MultipleStops) {
|
||||
ServerThread()->Start();
|
||||
Server()->Stop();
|
||||
Server()->Stop();
|
||||
ASSERT_TRUE(ServerThread()->JoinWithTimeout(5.0));
|
||||
}
|
||||
|
||||
TEST_F(ExceptionHandlerServerTest, RequestCrashDumpDefault) {
|
||||
ScopedStopServerAndJoinThread stop_server(Server(), ServerThread());
|
||||
ServerThread()->Start();
|
||||
|
||||
CrashDumpTest test(this, true);
|
||||
test.Run();
|
||||
}
|
||||
|
||||
TEST_F(ExceptionHandlerServerTest, RequestCrashDumpNoPtrace) {
|
||||
ExpectCrashDumpUsingStrategy(PtraceStrategyDecider::Strategy::kNoPtrace,
|
||||
false);
|
||||
}
|
||||
|
||||
TEST_F(ExceptionHandlerServerTest, RequestCrashDumpForkBroker) {
|
||||
ExpectCrashDumpUsingStrategy(PtraceStrategyDecider::Strategy::kUseBroker,
|
||||
true);
|
||||
}
|
||||
|
||||
TEST_F(ExceptionHandlerServerTest, RequestCrashDumpDirectPtrace) {
|
||||
ExpectCrashDumpUsingStrategy(PtraceStrategyDecider::Strategy::kDirectPtrace,
|
||||
true);
|
||||
}
|
||||
|
||||
TEST_F(ExceptionHandlerServerTest, RequestCrashDumpError) {
|
||||
ExpectCrashDumpUsingStrategy(PtraceStrategyDecider::Strategy::kError, false);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace test
|
||||
} // namespace crashpad
|
@ -14,6 +14,7 @@
|
||||
|
||||
#include "handler/mac/crash_report_exception_handler.h"
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "base/logging.h"
|
||||
@ -155,7 +156,7 @@ kern_return_t CrashReportExceptionHandler::CatchMachException(
|
||||
process_snapshot.SetClientID(client_id);
|
||||
process_snapshot.SetAnnotationsSimpleMap(*process_annotations_);
|
||||
|
||||
CrashReportDatabase::NewReport* new_report;
|
||||
std::unique_ptr<CrashReportDatabase::NewReport> new_report;
|
||||
CrashReportDatabase::OperationStatus database_status =
|
||||
database_->PrepareNewCrashReport(&new_report);
|
||||
if (database_status != CrashReportDatabase::kNoError) {
|
||||
@ -164,35 +165,31 @@ kern_return_t CrashReportExceptionHandler::CatchMachException(
|
||||
return KERN_FAILURE;
|
||||
}
|
||||
|
||||
process_snapshot.SetReportID(new_report->uuid);
|
||||
|
||||
CrashReportDatabase::CallErrorWritingCrashReport
|
||||
call_error_writing_crash_report(database_, new_report);
|
||||
|
||||
WeakFileHandleFileWriter file_writer(new_report->handle);
|
||||
process_snapshot.SetReportID(new_report->ReportID());
|
||||
|
||||
MinidumpFileWriter minidump;
|
||||
minidump.InitializeFromSnapshot(&process_snapshot);
|
||||
AddUserExtensionStreams(
|
||||
user_stream_data_sources_, &process_snapshot, &minidump);
|
||||
|
||||
if (!minidump.WriteEverything(&file_writer)) {
|
||||
if (!minidump.WriteEverything(new_report->Writer())) {
|
||||
Metrics::ExceptionCaptureResult(
|
||||
Metrics::CaptureResult::kMinidumpWriteFailed);
|
||||
return KERN_FAILURE;
|
||||
}
|
||||
|
||||
call_error_writing_crash_report.Disarm();
|
||||
|
||||
UUID uuid;
|
||||
database_status = database_->FinishedWritingCrashReport(new_report, &uuid);
|
||||
database_status =
|
||||
database_->FinishedWritingCrashReport(std::move(new_report), &uuid);
|
||||
if (database_status != CrashReportDatabase::kNoError) {
|
||||
Metrics::ExceptionCaptureResult(
|
||||
Metrics::CaptureResult::kFinishedWritingCrashReportFailed);
|
||||
return KERN_FAILURE;
|
||||
}
|
||||
|
||||
upload_thread_->ReportPending(uuid);
|
||||
if (upload_thread_) {
|
||||
upload_thread_->ReportPending(uuid);
|
||||
}
|
||||
}
|
||||
|
||||
if (client_options.system_crash_reporter_forwarding != TriState::kDisabled &&
|
||||
|
@ -36,7 +36,8 @@ class CrashReportExceptionHandler : public UniversalMachExcServer::Interface {
|
||||
//!
|
||||
//! \param[in] database The database to store crash reports in. Weak.
|
||||
//! \param[in] upload_thread The upload thread to notify when a new crash
|
||||
//! report is written into \a database.
|
||||
//! report is written into \a database. Report upload is skipped if this
|
||||
//! value is `nullptr`.
|
||||
//! \param[in] process_annotations A map of annotations to insert as
|
||||
//! process-level annotations into each crash report that is written. Do
|
||||
//! not confuse this with module-level annotations, which are under the
|
||||
|
@ -63,9 +63,7 @@ class ExceptionHandlerServer {
|
||||
|
||||
//! \brief Stops a running exception-handling server.
|
||||
//!
|
||||
//! The normal mode of operation is to call Stop() while Run() is running. It
|
||||
//! is expected that Stop() would be called from a signal handler.
|
||||
//!
|
||||
//! Stop() may be called at any time, and may be called from a signal handler.
|
||||
//! If Stop() is called before Run() it will cause Run() to return as soon as
|
||||
//! it is called. It is harmless to call Stop() after Run() has already
|
||||
//! returned, or to call Stop() after it has already been called.
|
||||
|
@ -21,7 +21,7 @@
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#if defined(OS_MACOSX)
|
||||
#if defined(OS_POSIX)
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
return crashpad::HandlerMain(argc, argv, nullptr);
|
||||
@ -50,4 +50,4 @@ int wmain(int argc, wchar_t* argv[]) {
|
||||
return crashpad::ToolSupport::Wmain(argc, argv, HandlerMainAdaptor);
|
||||
}
|
||||
|
||||
#endif // OS_MACOSX
|
||||
#endif // OS_POSIX
|
||||
|
86
handler/minidump_to_upload_parameters.cc
Normal file
86
handler/minidump_to_upload_parameters.cc
Normal file
@ -0,0 +1,86 @@
|
||||
// Copyright 2017 The Crashpad Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "handler/minidump_to_upload_parameters.h"
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "client/annotation.h"
|
||||
#include "snapshot/module_snapshot.h"
|
||||
#include "util/stdlib/map_insert.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
namespace {
|
||||
|
||||
void InsertOrReplaceMapEntry(std::map<std::string, std::string>* map,
|
||||
const std::string& key,
|
||||
const std::string& value) {
|
||||
std::string old_value;
|
||||
if (!MapInsertOrReplace(map, key, value, &old_value)) {
|
||||
LOG(WARNING) << "duplicate key " << key << ", discarding value "
|
||||
<< old_value;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::map<std::string, std::string> BreakpadHTTPFormParametersFromMinidump(
|
||||
const ProcessSnapshot* process_snapshot) {
|
||||
std::map<std::string, std::string> parameters =
|
||||
process_snapshot->AnnotationsSimpleMap();
|
||||
|
||||
std::string list_annotations;
|
||||
for (const ModuleSnapshot* module : process_snapshot->Modules()) {
|
||||
for (const auto& kv : module->AnnotationsSimpleMap()) {
|
||||
if (!parameters.insert(kv).second) {
|
||||
LOG(WARNING) << "duplicate key " << kv.first << ", discarding value "
|
||||
<< kv.second;
|
||||
}
|
||||
}
|
||||
|
||||
for (std::string annotation : module->AnnotationsVector()) {
|
||||
list_annotations.append(annotation);
|
||||
list_annotations.append("\n");
|
||||
}
|
||||
|
||||
for (const AnnotationSnapshot& annotation : module->AnnotationObjects()) {
|
||||
if (annotation.type != static_cast<uint16_t>(Annotation::Type::kString)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string value(reinterpret_cast<const char*>(annotation.value.data()),
|
||||
annotation.value.size());
|
||||
std::pair<std::string, std::string> entry(annotation.name, value);
|
||||
if (!parameters.insert(entry).second) {
|
||||
LOG(WARNING) << "duplicate annotation name " << annotation.name
|
||||
<< ", discarding value " << value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!list_annotations.empty()) {
|
||||
// Remove the final newline character.
|
||||
list_annotations.resize(list_annotations.size() - 1);
|
||||
|
||||
InsertOrReplaceMapEntry(¶meters, "list_annotations", list_annotations);
|
||||
}
|
||||
|
||||
UUID client_id;
|
||||
process_snapshot->ClientID(&client_id);
|
||||
InsertOrReplaceMapEntry(¶meters, "guid", client_id.ToString());
|
||||
|
||||
return parameters;
|
||||
}
|
||||
|
||||
} // namespace crashpad
|
61
handler/minidump_to_upload_parameters.h
Normal file
61
handler/minidump_to_upload_parameters.h
Normal file
@ -0,0 +1,61 @@
|
||||
// Copyright 2017 The Crashpad Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef HANDLER_MINIDUMP_TO_UPLOAD_PARAMETERS_H_
|
||||
#define HANDLER_MINIDUMP_TO_UPLOAD_PARAMETERS_H_
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "snapshot/process_snapshot.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
//! \brief Given a ProcessSnapshot, returns a map of key-value pairs to use as
|
||||
//! HTTP form parameters for upload to a Breakpad crash report colleciton
|
||||
//! server.
|
||||
//!
|
||||
//! The map is built by combining the process simple annotations map with
|
||||
//! each module’s simple annotations map and annotation objects.
|
||||
//!
|
||||
//! In the case of duplicate simple map keys or annotation names, the map will
|
||||
//! retain the first value found for any key, and will log a warning about
|
||||
//! discarded values. The precedence rules for annotation names are: the two
|
||||
//! reserved keys discussed below, process simple annotations, module simple
|
||||
//! annotations, and module annotation objects.
|
||||
//!
|
||||
//! For annotation objects, only ones of that are Annotation::Type::kString are
|
||||
//! included.
|
||||
//!
|
||||
//! Each module’s annotations vector is also examined and built into a single
|
||||
//! string value, with distinct elements separated by newlines, and stored at
|
||||
//! the key named “list_annotations”, which supersedes any other key found by
|
||||
//! that name.
|
||||
//!
|
||||
//! The client ID stored in the minidump is converted to a string and stored at
|
||||
//! the key named “guid”, which supersedes any other key found by that name.
|
||||
//!
|
||||
//! In the event of an error reading the minidump file, a message will be
|
||||
//! logged.
|
||||
//!
|
||||
//! \param[in] process_snapshot The process snapshot from which annotations
|
||||
//! will be extracted.
|
||||
//!
|
||||
//! \returns A string map of the annotations.
|
||||
std::map<std::string, std::string> BreakpadHTTPFormParametersFromMinidump(
|
||||
const ProcessSnapshot* process_snapshot);
|
||||
|
||||
} // namespace crashpad
|
||||
|
||||
#endif // HANDLER_MINIDUMP_TO_UPLOAD_PARAMETERS_H_
|
99
handler/minidump_to_upload_parameters_test.cc
Normal file
99
handler/minidump_to_upload_parameters_test.cc
Normal file
@ -0,0 +1,99 @@
|
||||
// Copyright 2017 The Crashpad Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "handler/minidump_to_upload_parameters.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "snapshot/test/test_module_snapshot.h"
|
||||
#include "snapshot/test/test_process_snapshot.h"
|
||||
#include "util/misc/uuid.h"
|
||||
|
||||
namespace crashpad {
|
||||
namespace test {
|
||||
namespace {
|
||||
|
||||
TEST(MinidumpToUploadParameters, PrecedenceRules) {
|
||||
const std::string guid = "00112233-4455-6677-8899-aabbccddeeff";
|
||||
UUID uuid;
|
||||
ASSERT_TRUE(uuid.InitializeFromString(guid));
|
||||
|
||||
TestProcessSnapshot process_snapshot;
|
||||
process_snapshot.SetClientID(uuid);
|
||||
process_snapshot.SetAnnotationsSimpleMap({
|
||||
{"process-1", "abcdefg"},
|
||||
{"list_annotations", "BAD: process_annotations"},
|
||||
{"guid", "BAD: process_annotations"},
|
||||
{"first", "process"},
|
||||
});
|
||||
|
||||
auto module_snapshot_0 = std::make_unique<TestModuleSnapshot>();
|
||||
module_snapshot_0->SetAnnotationsVector(
|
||||
{"list-module-0-1", "list-module-0-2"});
|
||||
module_snapshot_0->SetAnnotationsSimpleMap({
|
||||
{"module-0-1", "goat"},
|
||||
{"module-0-2", "doge"},
|
||||
{"list_annotations", "BAD: module 0"},
|
||||
{"guid", "BAD: module 0"},
|
||||
{"first", "BAD: module 0"},
|
||||
{"second", "module 0"},
|
||||
});
|
||||
module_snapshot_0->SetAnnotationObjects({
|
||||
{"module-0-3", 1, {'s', 't', 'a', 'r'}},
|
||||
{"module-0-4", 0xFFFA, {0x42}},
|
||||
{"guid", 1, {'B', 'A', 'D', '*', '0', '-', '0'}},
|
||||
{"list_annotations", 1, {'B', 'A', 'D', '*', '0', '-', '1'}},
|
||||
{"first", 1, {'B', 'A', 'D', '*', '0', '-', '2'}},
|
||||
});
|
||||
process_snapshot.AddModule(std::move(module_snapshot_0));
|
||||
|
||||
auto module_snapshot_1 = std::make_unique<TestModuleSnapshot>();
|
||||
module_snapshot_1->SetAnnotationsVector(
|
||||
{"list-module-1-1", "list-module-1-2"});
|
||||
module_snapshot_1->SetAnnotationsSimpleMap({
|
||||
{"module-1-1", "bear"},
|
||||
{"list_annotations", "BAD: module 1"},
|
||||
{"guid", "BAD: module 1"},
|
||||
{"first", "BAD: module 1"},
|
||||
{"second", "BAD: module 1"},
|
||||
});
|
||||
module_snapshot_1->SetAnnotationObjects({
|
||||
{"module-1-3", 0xBEEF, {'a', 'b', 'c'}},
|
||||
{"module-1-4", 1, {'m', 'o', 'o', 'n'}},
|
||||
{"guid", 1, {'B', 'A', 'D', '*', '1', '-', '0'}},
|
||||
{"list_annotations", 1, {'B', 'A', 'D', '*', '1', '-', '1'}},
|
||||
{"second", 1, {'B', 'A', 'D', '*', '1', '-', '2'}},
|
||||
});
|
||||
process_snapshot.AddModule(std::move(module_snapshot_1));
|
||||
|
||||
auto upload_parameters =
|
||||
BreakpadHTTPFormParametersFromMinidump(&process_snapshot);
|
||||
|
||||
EXPECT_EQ(upload_parameters.size(), 10u);
|
||||
EXPECT_EQ(upload_parameters["process-1"], "abcdefg");
|
||||
EXPECT_EQ(upload_parameters["first"], "process");
|
||||
EXPECT_EQ(upload_parameters["module-0-1"], "goat");
|
||||
EXPECT_EQ(upload_parameters["module-0-2"], "doge");
|
||||
EXPECT_EQ(upload_parameters["module-0-3"], "star");
|
||||
EXPECT_EQ(upload_parameters["second"], "module 0");
|
||||
EXPECT_EQ(upload_parameters["module-1-1"], "bear");
|
||||
EXPECT_EQ(upload_parameters["module-1-4"], "moon");
|
||||
EXPECT_EQ(upload_parameters["list_annotations"],
|
||||
"list-module-0-1\nlist-module-0-2\n"
|
||||
"list-module-1-1\nlist-module-1-2");
|
||||
EXPECT_EQ(upload_parameters["guid"], guid);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace test
|
||||
} // namespace crashpad
|
@ -38,6 +38,7 @@ void PruneCrashReportThread::Stop() {
|
||||
}
|
||||
|
||||
void PruneCrashReportThread::DoWork(const WorkerThread* thread) {
|
||||
database_->CleanDatabase(60 * 60 * 24 * 3);
|
||||
PruneCrashReportDatabase(database_, condition_.get());
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include <memory>
|
||||
|
||||
#include "base/macros.h"
|
||||
#include "util/thread/stoppable.h"
|
||||
#include "util/thread/worker_thread.h"
|
||||
|
||||
namespace crashpad {
|
||||
@ -31,7 +32,7 @@ class PruneCondition;
|
||||
//! After the thread is started, the database is pruned using the condition
|
||||
//! every 24 hours. Upon calling Start(), the thread waits 10 minutes before
|
||||
//! performing the initial prune operation.
|
||||
class PruneCrashReportThread : public WorkerThread::Delegate {
|
||||
class PruneCrashReportThread : public WorkerThread::Delegate, public Stoppable {
|
||||
public:
|
||||
//! \brief Constructs a new object.
|
||||
//!
|
||||
@ -42,6 +43,8 @@ class PruneCrashReportThread : public WorkerThread::Delegate {
|
||||
std::unique_ptr<PruneCondition> condition);
|
||||
~PruneCrashReportThread();
|
||||
|
||||
// Stoppable:
|
||||
|
||||
//! \brief Starts a dedicated pruning thread.
|
||||
//!
|
||||
//! The thread waits before running the initial prune, so as to not interfere
|
||||
@ -49,7 +52,7 @@ class PruneCrashReportThread : public WorkerThread::Delegate {
|
||||
//!
|
||||
//! This method may only be be called on a newly-constructed object or after
|
||||
//! a call to Stop().
|
||||
void Start();
|
||||
void Start() override;
|
||||
|
||||
//! \brief Stops the pruning thread.
|
||||
//!
|
||||
@ -58,7 +61,7 @@ class PruneCrashReportThread : public WorkerThread::Delegate {
|
||||
//!
|
||||
//! This method may be called from any thread other than the pruning thread.
|
||||
//! It is expected to only be called from the same thread that called Start().
|
||||
void Stop();
|
||||
void Stop() override;
|
||||
|
||||
private:
|
||||
// WorkerThread::Delegate:
|
||||
|
@ -33,7 +33,7 @@ namespace {
|
||||
|
||||
constexpr DWORD kCrashAndDumpTargetExitCode = 0xdeadbea7;
|
||||
|
||||
bool CrashAndDumpTarget(const CrashpadClient& client, HANDLE process) {
|
||||
bool CrashAndDumpTarget(HANDLE process) {
|
||||
DWORD target_pid = GetProcessId(process);
|
||||
|
||||
ScopedFileHANDLE thread_snap(CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0));
|
||||
@ -52,9 +52,8 @@ bool CrashAndDumpTarget(const CrashpadClient& client, HANDLE process) {
|
||||
do {
|
||||
if (te32.th32OwnerProcessID == target_pid) {
|
||||
// We set the thread priority of "Thread1" to a non-default value before
|
||||
// going to sleep. Dump and blame this thread. For an explanation of
|
||||
// "9", see
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms685100.aspx.
|
||||
// going to sleep. Dump and blame this thread. For an explanation of "9",
|
||||
// see https://msdn.microsoft.com/library/ms685100.aspx.
|
||||
if (te32.tpBasePri == 9) {
|
||||
ScopedKernelHANDLE thread(
|
||||
OpenThread(kXPThreadAllAccess, false, te32.th32ThreadID));
|
||||
@ -62,7 +61,7 @@ bool CrashAndDumpTarget(const CrashpadClient& client, HANDLE process) {
|
||||
PLOG(ERROR) << "OpenThread";
|
||||
return false;
|
||||
}
|
||||
if (!client.DumpAndCrashTargetProcess(
|
||||
if (!CrashpadClient::DumpAndCrashTargetProcess(
|
||||
process, thread.get(), kCrashAndDumpTargetExitCode)) {
|
||||
return false;
|
||||
}
|
||||
@ -110,11 +109,12 @@ int CrashOtherProgram(int argc, wchar_t* argv[]) {
|
||||
DWORD expect_exit_code;
|
||||
if (argc == 3 && wcscmp(argv[2], L"noexception") == 0) {
|
||||
expect_exit_code = CrashpadClient::kTriggeredExceptionCode;
|
||||
if (!client.DumpAndCrashTargetProcess(child.process_handle(), 0, 0))
|
||||
if (!CrashpadClient::DumpAndCrashTargetProcess(
|
||||
child.process_handle(), 0, 0))
|
||||
return EXIT_FAILURE;
|
||||
} else {
|
||||
expect_exit_code = kCrashAndDumpTargetExitCode;
|
||||
if (!CrashAndDumpTarget(client, child.process_handle())) {
|
||||
if (!CrashAndDumpTarget(child.process_handle())) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
@ -122,7 +122,7 @@ int CrashOtherProgram(int argc, wchar_t* argv[]) {
|
||||
DWORD exit_code = child.WaitForExit();
|
||||
if (exit_code != expect_exit_code) {
|
||||
LOG(ERROR) << base::StringPrintf(
|
||||
"incorrect exit code, expected 0x%x, observed 0x%x",
|
||||
"incorrect exit code, expected 0x%lx, observed 0x%lx",
|
||||
expect_exit_code,
|
||||
exit_code);
|
||||
return EXIT_FAILURE;
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include "handler/win/crash_report_exception_handler.h"
|
||||
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "client/crash_report_database.h"
|
||||
#include "client/settings.h"
|
||||
@ -59,7 +60,6 @@ unsigned int CrashReportExceptionHandler::ExceptionHandlerServerException(
|
||||
ProcessSuspensionState::kSuspended,
|
||||
exception_information_address,
|
||||
debug_critical_section_address)) {
|
||||
LOG(WARNING) << "ProcessSnapshotWin::Initialize failed";
|
||||
Metrics::ExceptionCaptureResult(Metrics::CaptureResult::kSnapshotFailed);
|
||||
return kTerminationCodeSnapshotFailed;
|
||||
}
|
||||
@ -90,7 +90,7 @@ unsigned int CrashReportExceptionHandler::ExceptionHandlerServerException(
|
||||
process_snapshot.SetClientID(client_id);
|
||||
process_snapshot.SetAnnotationsSimpleMap(*process_annotations_);
|
||||
|
||||
CrashReportDatabase::NewReport* new_report;
|
||||
std::unique_ptr<CrashReportDatabase::NewReport> new_report;
|
||||
CrashReportDatabase::OperationStatus database_status =
|
||||
database_->PrepareNewCrashReport(&new_report);
|
||||
if (database_status != CrashReportDatabase::kNoError) {
|
||||
@ -100,29 +100,23 @@ unsigned int CrashReportExceptionHandler::ExceptionHandlerServerException(
|
||||
return termination_code;
|
||||
}
|
||||
|
||||
process_snapshot.SetReportID(new_report->uuid);
|
||||
|
||||
CrashReportDatabase::CallErrorWritingCrashReport
|
||||
call_error_writing_crash_report(database_, new_report);
|
||||
|
||||
WeakFileHandleFileWriter file_writer(new_report->handle);
|
||||
process_snapshot.SetReportID(new_report->ReportID());
|
||||
|
||||
MinidumpFileWriter minidump;
|
||||
minidump.InitializeFromSnapshot(&process_snapshot);
|
||||
AddUserExtensionStreams(
|
||||
user_stream_data_sources_, &process_snapshot, &minidump);
|
||||
|
||||
if (!minidump.WriteEverything(&file_writer)) {
|
||||
if (!minidump.WriteEverything(new_report->Writer())) {
|
||||
LOG(ERROR) << "WriteEverything failed";
|
||||
Metrics::ExceptionCaptureResult(
|
||||
Metrics::CaptureResult::kMinidumpWriteFailed);
|
||||
return termination_code;
|
||||
}
|
||||
|
||||
call_error_writing_crash_report.Disarm();
|
||||
|
||||
UUID uuid;
|
||||
database_status = database_->FinishedWritingCrashReport(new_report, &uuid);
|
||||
database_status =
|
||||
database_->FinishedWritingCrashReport(std::move(new_report), &uuid);
|
||||
if (database_status != CrashReportDatabase::kNoError) {
|
||||
LOG(ERROR) << "FinishedWritingCrashReport failed";
|
||||
Metrics::ExceptionCaptureResult(
|
||||
@ -130,7 +124,9 @@ unsigned int CrashReportExceptionHandler::ExceptionHandlerServerException(
|
||||
return termination_code;
|
||||
}
|
||||
|
||||
upload_thread_->ReportPending(uuid);
|
||||
if (upload_thread_) {
|
||||
upload_thread_->ReportPending(uuid);
|
||||
}
|
||||
}
|
||||
|
||||
Metrics::ExceptionCaptureResult(Metrics::CaptureResult::kSuccess);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user