Merge master e50ea6032180 into doc

This commit is contained in:
Mark Mentovai 2018-07-20 16:32:19 -04:00
commit c46a62337d
499 changed files with 33099 additions and 5076 deletions

9
.gitignore vendored
View File

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

View File

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

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

View 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" ]
}
}
}

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

View 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) {
}
}
}

View File

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

View File

@ -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__ isnt 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 wont mention
# __ANDROID_API__, but the standalone toolchains <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__
# isnt set automatically and must be pushed in to the build. Fish the correct
# value out of the Clang wrapper script. If deprecated headers are in use, the
# Clang wrapper wont mention __ANDROID_API__, but the standalone toolchains
# <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 its available for use in GYP conditions that need to test the API
# level, but beware that itll only be available when unified headers are in
# use.
#
# Unified headers are the way of the future, according to
# https://android.googlesource.com/platform/ndk/+/ndk-r14/CHANGELOG.md and
# https://android.googlesource.com/platform/ndk/+/master/docs/UnifiedHeaders.md.
# Traditional (deprecated) headers have been removed entirely as of NDK r16.
# https://android.googlesource.com/platform/ndk/+/ndk-release-r16/CHANGELOG.md.
with open(clang_path, 'r') as file:
clang_script_contents = file.read()
matches = re.finditer(r'\s-D__ANDROID_API__=([\d]+)\s',
clang_script_contents)
match = next(matches, None)
if match:
android_api = int(match.group(1))
extra_args.extend(['-D', 'android_api_level=%d' % android_api])
if next(matches, None):
raise AssertionError('__ANDROID_API__ defined too many times')
for tool in ('ar', 'nm', 'readelf'):
os.environ['%s_target' % tool.upper()] = (

74
build/install_linux_sysroot.py Executable file
View 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
View 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:]))

View File

@ -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 its 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 isnt 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 scripts 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.
# Heres why:
#
# /system/bin/env isnt normally present prior to Android 6.0 (M), where
# toybox was introduced (Android platform/manifest 9a2c01e8450b). Instead,
# set environment variables by using the shells internal “export” command.
#
# adbd prior to Android 7.0 (N), and the adb client prior to SDK
# platform-tools version 24, dont know how to communicate a shell commands
# 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 commands 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 wasnt 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 isnt normally present prior to Android 6.0 (M), where
# toybox was introduced (Android platform/manifest 9a2c01e8450b). Fake it with
# a host-generated name. This wont 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 whats needed from the path split. All parent
# directories of any source_path need to be be represented in
# device_mkdirs, but its 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 gtests own logic for deciding whether to enable color by checking
# this scripts 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 its 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
View 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
View 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" ]
}
}

View File

@ -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() {

View File

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

View File

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

View File

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

View File

@ -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 its valid, such as
// magic numbers or size fields, sanity-checks those fields for validity with
// fatal gtest assertions. For other fields, where its 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 Developers Manual, Volume 1: Basic Architecture (253665-051),
// 3.4.3 “EFLAGS Register”, and AMD Architecture Programmers 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 functions 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, theres 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

View File

@ -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$'],
],
}],
],

View 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

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

View File

@ -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$'],
],
}],
],
},
],
}

View File

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

View File

@ -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 records 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 records 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);
};

View 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(), &timestamp, 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(), &timestamp, 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

View File

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

View File

@ -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(&times[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(&times[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

View File

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

View File

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

View 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

View 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

View 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

View File

@ -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 arent needed until
// execve() is called, but its 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 cant 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 shouldnt 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 cant 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 cant 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.
//
// Its 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 clients 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)) {

View File

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

View File

@ -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>();
}

View File

@ -25,6 +25,11 @@
namespace {
// Dont change this when simply adding fields. Readers will size-check the
// structure and ignore fields theyre aware of when not present, as well as
// fields theyre 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 its 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,
// Theres 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,

View File

@ -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
// Its generally safe to add new fields without changing
// kCrashpadInfoVersion, because readers should check size_ and ignore fields
// that arent present, as well as unknown fields.
//
// Adding fields? Consider snapshot/crashpad_info_size_test_module.cc too.
#if defined(__clang__)
#pragma clang diagnostic pop

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

@ -17,7 +17,7 @@
#include <mach/mach.h>
#include "client/capture_context_mac.h"
#include "util/misc/capture_context.h"
//! \file

View 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
View 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 doesnt 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" ]
}
}

View 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

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

View File

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

View 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

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

View File

@ -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 systems 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);
}

View File

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

View File

@ -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',
],

View File

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

View File

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

View File

@ -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, theyre 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 systems versions are used directly. Otherwise, the versions in
// third_party/apple_cctools are used, which are actually just copies of the
// systems 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

View File

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

View File

@ -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 modules
//! IMAGE_DEBUG_DIRECTORY entry whose Type field has the value

View File

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

View File

@ -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 Chromiums. 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/).
* Chromiums
[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`, Crashpads 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). Theres
[depot_tools](https://www.chromium.org/developers/how-tos/depottools). Theres
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). Theres
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). Theres
no need to install them separately.
### Android
@ -119,7 +116,7 @@ Kit)](https://developer.android.com/ndk/) runs on.
If its not already present on your system, [download the NDK package for your
system](https://developer.android.com/ndk/downloads/) and expand it to a
suitable location. These instructions assume that its 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
Crashpads contribution process is very similar to [Chromiums 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). Theres
[depot_tools](https://www.chromium.org/developers/how-tos/depottools). Theres
no need to install it separately.
```
@ -282,7 +262,7 @@ patch set with `git cl upload` and let your reviewer know youve 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 Crashpads [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.

View File

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

View File

@ -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
View 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. Dont 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",
]
}
}
}

View File

@ -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 modules 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 modules 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(&parameters, "list_annotations", list_annotations);
}
UUID client_id;
minidump_process_snapshot.ClientID(&client_id);
InsertOrReplaceMapEntry(&parameters, "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)) {
// Dont attempt an upload if theres 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 databases 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: dont call SkipReportUplaod()
// if the result was kRetry and the report hasnt 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 cant 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 theres 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) {

View File

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

View File

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

View File

@ -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>();
}

View File

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

View 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

View 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 its able to establish its own annotations.
//! To interoperate with Breakpad servers, the recommended practice is to
//! specify values for the `"prod"` and `"ver"` keys as process
//! annotations.
//! \param[in] process_attachments A map of file name keys to file paths to be
//! included in the report. Each time a report is written, the file paths
//! will be read in their entirety and included in the report using the
//! file name key as the name in the http upload.
//! \param[in] user_stream_data_sources Data sources to be used to extend
//! crash reports. For each crash report that is written, the data sources
//! are called in turn. These data sources may contribute additional
//! minidump streams. `nullptr` if not required.
CrashReportExceptionHandler(
CrashReportDatabase* database,
CrashReportUploadThread* upload_thread,
const std::map<std::string, std::string>* process_annotations,
const std::map<std::string, base::FilePath>* process_attachments,
const UserStreamDataSources* user_stream_data_sources);
~CrashReportExceptionHandler();
//! \brief Called when the exception handler server has caught an exception
//! and wants a crash dump to be taken.
//!
//! This function is expected to call `zx_task_resume()` in order to complete
//! handling of the exception.
//!
//! \param[in] process_id The koid of the process which sustained the
//! exception.
//! \param[in] thread_id The koid of the thread which sustained the exception.
//! \return `true` on success, or `false` with an error logged.
bool HandleException(uint64_t process_id, uint64_t thread_id);
//! \brief Called when the exception handler server has caught an exception
//! and wants a crash dump to be taken.
//!
//! This function is expected to call `zx_task_resume()` in order to complete
//! handling of the exception.
//!
//! \param[in] process The handle to the process which sustained the
//! exception.
//! \param[in] thread The handle to the thread of \a process which sustained
//! the exception.
//! \return `true` on success, or `false` with an error logged.
bool HandleExceptionHandles(zx_handle_t process, zx_handle_t thread);
private:
CrashReportDatabase* database_; // weak
CrashReportUploadThread* upload_thread_; // weak
const std::map<std::string, std::string>* process_annotations_; // weak
const std::map<std::string, base::FilePath>* process_attachments_; // weak
const UserStreamDataSources* user_stream_data_sources_; // weak
DISALLOW_COPY_AND_ASSIGN(CrashReportExceptionHandler);
};
} // namespace crashpad
#endif // CRASHPAD_HANDLER_FUCHSIA_CRASH_REPORT_EXCEPTION_HANDLER_H_

View File

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

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

View File

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

View File

@ -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()) {
// Dont 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;
}

View File

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

View 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

View 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 Chromes
//! “crash keys.” Process-level annotations are those that are beyond the
//! control of the crashing process, which must reliably be set even if
//! the process crashes before its able to establish its own annotations.
//! To interoperate with Breakpad servers, the recommended practice is to
//! specify values for the `"prod"` and `"ver"` keys as process
//! annotations.
//! \param[in] user_stream_data_sources Data sources to be used to extend
//! crash reports. For each crash report that is written, the data sources
//! are called in turn. These data sources may contribute additional
//! minidump streams. `nullptr` if not required.
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_

View 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

View 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 Crashpads 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_

View 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

View File

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

View File

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

View File

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

View File

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

View 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(&parameters, "list_annotations", list_annotations);
}
UUID client_id;
process_snapshot->ClientID(&client_id);
InsertOrReplaceMapEntry(&parameters, "guid", client_id.ToString());
return parameters;
}
} // namespace crashpad

View 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 modules 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 modules 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_

View 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

View File

@ -38,6 +38,7 @@ void PruneCrashReportThread::Stop() {
}
void PruneCrashReportThread::DoWork(const WorkerThread* thread) {
database_->CleanDatabase(60 * 60 * 24 * 3);
PruneCrashReportDatabase(database_, condition_.get());
}

View File

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

View File

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

View File

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