From 9ed82905471121a59fb8827627df3a98b7cdfe55 Mon Sep 17 00:00:00 2001 From: Justin Cohen Date: Tue, 18 Feb 2020 14:49:10 -0500 Subject: [PATCH] Bring up skeleton crashpad_client_ios. First steps at bringing up the crashpad_client on iOS. Also updates the XCUITest to trigger various crashes, with some swizzling necessary to allow crashes. Change-Id: I87dd36bed1c052b509d14bfa29679ed81e58a377 Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/2039470 Commit-Queue: Justin Cohen Reviewed-by: Mark Mentovai Reviewed-by: Rohit Rao --- client/BUILD.gn | 8 +- client/crashpad_client.h | 14 ++- client/crashpad_client_ios.cc | 75 +++++++++++ compat/BUILD.gn | 6 +- handler/BUILD.gn | 60 ++++----- test/ios/BUILD.gn | 1 + test/ios/crash_type_xctest.mm | 117 +++++++++++++++++- test/ios/host/BUILD.gn | 13 +- test/ios/host/application_delegate.mm | 35 +++++- ...o_placeholder.h => cptest_shared_object.h} | 24 +++- tools/BUILD.gn | 47 +++---- util/stdlib/strnlen.cc | 3 +- util/stdlib/strnlen.h | 4 +- 13 files changed, 327 insertions(+), 80 deletions(-) create mode 100644 client/crashpad_client_ios.cc rename test/ios/host/{edo_placeholder.h => cptest_shared_object.h} (54%) diff --git a/client/BUILD.gn b/client/BUILD.gn index a61c66d0..4ec03863 100644 --- a/client/BUILD.gn +++ b/client/BUILD.gn @@ -43,6 +43,10 @@ static_library("client") { ] } + if (crashpad_is_ios) { + sources += [ "crashpad_client_ios.cc" ] + } + if (crashpad_is_linux || crashpad_is_android) { set_sources_assignment_filter([]) sources += [ @@ -137,7 +141,9 @@ source_set("client_test") { "../util", ] - data_deps = [ "../handler:crashpad_handler" ] + if (!crashpad_is_ios) { + data_deps = [ "../handler:crashpad_handler" ] + } if (crashpad_is_win) { data_deps += [ "../handler:crashpad_handler_console" ] diff --git a/client/crashpad_client.h b/client/crashpad_client.h index 207f9784..e0cd2f1f 100644 --- a/client/crashpad_client.h +++ b/client/crashpad_client.h @@ -423,9 +423,21 @@ class CrashpadClient { //! //! \param[in] unhandled_signals The set of unhandled signals void SetUnhandledSignals(const std::set& unhandled_signals); - #endif // OS_LINUX || OS_ANDROID || DOXYGEN +#if defined(OS_IOS) || DOXYGEN + //! \brief Configures the process to direct its crashes to the iOS in-process + //! Crashpad handler. + //! + //! This method is only defined on iOS. + //! + //! \return `true` on success, `false` on failure with a message logged. + //! + //! TODO(justincohen): This method will need to take database, metrics_dir, + //! url and annotations eventually. + bool StartCrashpadInProcessHandler(); +#endif + #if defined(OS_MACOSX) || DOXYGEN //! \brief Sets the process’ crash handler to a Mach service registered with //! the bootstrap server. diff --git a/client/crashpad_client_ios.cc b/client/crashpad_client_ios.cc new file mode 100644 index 00000000..c19152b8 --- /dev/null +++ b/client/crashpad_client_ios.cc @@ -0,0 +1,75 @@ +// Copyright 2020 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "client/crashpad_client.h" + +#include + +#include "base/logging.h" +#include "base/strings/stringprintf.h" +#include "client/client_argv_handling.h" +#include "util/posix/signals.h" + +namespace crashpad { + +namespace { + +// A base class for Crashpad signal handler implementations. +class SignalHandler { + public: + // Returns the currently installed signal hander. + static SignalHandler* Get() { + static SignalHandler* instance = new SignalHandler(); + return instance; + } + + bool Install(const std::set* unhandled_signals) { + return Signals::InstallCrashHandlers( + HandleSignal, 0, &old_actions_, unhandled_signals); + } + + private: + SignalHandler() = default; + + // The base implementation for all signal handlers, suitable for calling + // directly to simulate signal delivery. + void HandleCrash(int signo, siginfo_t* siginfo, void* context) { + // Do Something. + + // Always call system handler. + Signals::RestoreHandlerAndReraiseSignalOnReturn( + siginfo, old_actions_.ActionForSignal(signo)); + } + + // The signal handler installed at OS-level. + static void HandleSignal(int signo, siginfo_t* siginfo, void* context) { + Get()->HandleCrash(signo, siginfo, context); + } + + Signals::OldActions old_actions_ = {}; + + DISALLOW_COPY_AND_ASSIGN(SignalHandler); +}; + +} // namespace + +CrashpadClient::CrashpadClient() {} + +CrashpadClient::~CrashpadClient() {} + +bool CrashpadClient::StartCrashpadInProcessHandler() { + return SignalHandler::Get()->Install(nullptr); +} + +} // namespace crashpad diff --git a/compat/BUILD.gn b/compat/BUILD.gn index 62ee5286..2f246565 100644 --- a/compat/BUILD.gn +++ b/compat/BUILD.gn @@ -19,7 +19,7 @@ config("compat_config") { if (crashpad_is_mac) { include_dirs += [ "mac" ] - } else { + } else if (!crashpad_is_ios) { include_dirs += [ "non_mac" ] } @@ -43,7 +43,7 @@ config("compat_config") { } template("compat_target") { - if (crashpad_is_mac) { + if (crashpad_is_mac || crashpad_is_ios) { # There are no sources to compile, which doesn’t mix will with a # static_library. group(target_name) { @@ -67,7 +67,7 @@ compat_target("compat") { "mac/mach/mach.h", "mac/sys/resource.h", ] - } else { + } else if (!crashpad_is_ios) { sources += [ "non_mac/mach-o/loader.h", "non_mac/mach/mach.h", diff --git a/handler/BUILD.gn b/handler/BUILD.gn index 8862faba..3db3c899 100644 --- a/handler/BUILD.gn +++ b/handler/BUILD.gn @@ -140,25 +140,27 @@ source_set("handler_test") { } } -crashpad_executable("crashpad_handler") { - sources = [ "main.cc" ] +if (!crashpad_is_ios) { + crashpad_executable("crashpad_handler") { + sources = [ "main.cc" ] - deps = [ - ":handler", - "../build:default_exe_manifest_win", - "../compat", - "../third_party/mini_chromium:base", - ] + deps = [ + ":handler", + "../build:default_exe_manifest_win", + "../compat", + "../third_party/mini_chromium:base", + ] - if (crashpad_is_win) { - if (crashpad_is_in_chromium || crashpad_is_in_dart) { - remove_configs = [ "//build/config/win:console" ] - configs = [ "//build/config/win:windowed" ] - } else { - remove_configs = - [ "//third_party/mini_chromium/mini_chromium/build:win_console" ] - configs = - [ "//third_party/mini_chromium/mini_chromium/build:win_windowed" ] + if (crashpad_is_win) { + if (crashpad_is_in_chromium || crashpad_is_in_dart) { + remove_configs = [ "//build/config/win:console" ] + configs = [ "//build/config/win:windowed" ] + } else { + remove_configs = + [ "//third_party/mini_chromium/mini_chromium/build:win_console" ] + configs = + [ "//third_party/mini_chromium/mini_chromium/build:win_windowed" ] + } } } } @@ -190,19 +192,21 @@ if (crashpad_is_android) { } } -crashpad_executable("crashpad_handler_test_extended_handler") { - testonly = true +if (!crashpad_is_ios) { + crashpad_executable("crashpad_handler_test_extended_handler") { + testonly = true - sources = [ "crashpad_handler_test_extended_handler.cc" ] + 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", - ] + deps = [ + ":handler", + "../build:default_exe_manifest_win", + "../compat", + "../minidump:test_support", + "../third_party/mini_chromium:base", + "../tools:tool_support", + ] + } } if (crashpad_is_win) { diff --git a/test/ios/BUILD.gn b/test/ios/BUILD.gn index 225bf2a2..d548a279 100644 --- a/test/ios/BUILD.gn +++ b/test/ios/BUILD.gn @@ -40,6 +40,7 @@ source_set("google_test_runner") { deps = [ "../../build:ios_enable_arc", "../../build:ios_xctest", + "../../test/ios:google_test_runner_shared_headers", "../../third_party/mini_chromium:base", ] libs = [ "UIKit.framework" ] diff --git a/test/ios/crash_type_xctest.mm b/test/ios/crash_type_xctest.mm index ed72f613..8ebdafa4 100644 --- a/test/ios/crash_type_xctest.mm +++ b/test/ios/crash_type_xctest.mm @@ -14,26 +14,135 @@ #import +#include #import "Service/Sources/EDOClientService.h" -#import "test/ios/host/edo_placeholder.h" +#import "test/ios/host/cptest_shared_object.h" #if !defined(__has_feature) || !__has_feature(objc_arc) #error "This file requires ARC support." #endif -@interface CPTestTestCase : XCTestCase +@interface CPTestTestCase : XCTestCase { + XCUIApplication* _app; +} + @end @implementation CPTestTestCase +- (void)handleCrashUnderSymbol:(id)arg1 { + // For now, do nothing. In the future this can be something testable. +} + ++ (void)setUp { + // Swizzle away the handleCrashUnderSymbol callback. Without this, any time + // the host app is intentionally crashed, the test is immediately failed. + SEL originalSelector = NSSelectorFromString(@"handleCrashUnderSymbol:"); + SEL swizzledSelector = @selector(handleCrashUnderSymbol:); + + Method originalMethod = class_getInstanceMethod( + objc_getClass("XCUIApplicationImpl"), originalSelector); + Method swizzledMethod = + class_getInstanceMethod([self class], swizzledSelector); + + method_exchangeImplementations(originalMethod, swizzledMethod); + + // Override EDO default error handler. Without this, the default EDO error + // handler will throw an error and fail the test. + [EDOClientService setErrorHandler:^(NSError* error){ + // Do nothing. + }]; +} + - (void)setUp { - [[[XCUIApplication alloc] init] launch]; + _app = [[XCUIApplication alloc] init]; + [_app launch]; } - (void)testEDO { - EDOPlaceholder* rootObject = [EDOClientService rootObjectWithPort:12345]; + CPTestSharedObject* rootObject = [EDOClientService rootObjectWithPort:12345]; NSString* result = [rootObject testEDO]; XCTAssertEqualObjects(result, @"crashpad"); } +- (void)testSegv { + XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground); + + // Crash the app. + CPTestSharedObject* rootObject = [EDOClientService rootObjectWithPort:12345]; + [rootObject crashSegv]; + + // Confirm the app is not running. + XCTAssertTrue([_app waitForState:XCUIApplicationStateNotRunning timeout:15]); + XCTAssertTrue(_app.state == XCUIApplicationStateNotRunning); + + // TODO: Query the app for crash data + [_app launch]; + XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground); +} + +- (void)testKillAbort { + XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground); + + // Crash the app. + CPTestSharedObject* rootObject = [EDOClientService rootObjectWithPort:12345]; + [rootObject crashKillAbort]; + + // Confirm the app is not running. + XCTAssertTrue([_app waitForState:XCUIApplicationStateNotRunning timeout:15]); + XCTAssertTrue(_app.state == XCUIApplicationStateNotRunning); + + // TODO: Query the app for crash data + [_app launch]; + XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground); +} + +- (void)testTrap { + XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground); + + // Crash the app. + CPTestSharedObject* rootObject = [EDOClientService rootObjectWithPort:12345]; + [rootObject crashTrap]; + + // Confirm the app is not running. + XCTAssertTrue([_app waitForState:XCUIApplicationStateNotRunning timeout:15]); + XCTAssertTrue(_app.state == XCUIApplicationStateNotRunning); + + // TODO: Query the app for crash data + [_app launch]; + XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground); +} + +- (void)testAbort { + XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground); + + // Crash the app. + CPTestSharedObject* rootObject = [EDOClientService rootObjectWithPort:12345]; + [rootObject crashAbort]; + + // Confirm the app is not running. + XCTAssertTrue([_app waitForState:XCUIApplicationStateNotRunning timeout:15]); + XCTAssertTrue(_app.state == XCUIApplicationStateNotRunning); + + // TODO: Query the app for crash data + [_app launch]; + XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground); +} + +- (void)testBadAccess { + XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground); + + // Crash the app. + CPTestSharedObject* rootObject = [EDOClientService rootObjectWithPort:12345]; + [rootObject crashBadAccess]; + + // Confirm the app is not running. + XCTAssertTrue([_app waitForState:XCUIApplicationStateNotRunning timeout:15]); + XCTAssertTrue(_app.state == XCUIApplicationStateNotRunning); + + // TODO: Query the app for crash data + [_app launch]; + XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground); +} + @end diff --git a/test/ios/host/BUILD.gn b/test/ios/host/BUILD.gn index 39c8d999..e0d289d4 100644 --- a/test/ios/host/BUILD.gn +++ b/test/ios/host/BUILD.gn @@ -22,13 +22,9 @@ if (crashpad_is_in_chromium) { source_set("app_shared_sources") { testonly = true - sources = [ - "edo_placeholder.h", - ] + sources = [ "cptest_shared_object.h" ] configs += [ "../../..:crashpad_config" ] - deps = [ - "../../../build:ios_enable_arc", - ] + deps = [ "../../../build:ios_enable_arc" ] libs = [ "UIKit.framework" ] } @@ -45,6 +41,7 @@ static_library("app_host_sources") { deps = [ ":app_shared_sources", "../../../build:ios_enable_arc", + "../../../client", "../../../third_party/edo", ] libs = [ @@ -56,7 +53,5 @@ static_library("app_host_sources") { ios_app_bundle("ios_crash_xcuitests") { info_plist = "Info.plist" testonly = true - deps = [ - ":app_host_sources", - ] + deps = [ ":app_host_sources" ] } diff --git a/test/ios/host/application_delegate.mm b/test/ios/host/application_delegate.mm index 115d0ae5..a31d4d67 100644 --- a/test/ios/host/application_delegate.mm +++ b/test/ios/host/application_delegate.mm @@ -16,8 +16,9 @@ #import "Service/Sources/EDOHostNamingService.h" #import "Service/Sources/EDOHostService.h" +#include "client/crashpad_client.h" +#import "test/ios/host/cptest_shared_object.h" #import "test/ios/host/crash_view_controller.h" -#import "test/ios/host/edo_placeholder.h" #if !defined(__has_feature) || !__has_feature(objc_arc) #error "This file requires ARC support." @@ -28,6 +29,10 @@ - (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { + // Start up crashpad. + crashpad::CrashpadClient client; + client.StartCrashpadInProcessHandler(); + self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; [self.window makeKeyAndVisible]; self.window.backgroundColor = UIColor.greenColor; @@ -37,17 +42,37 @@ // Start up EDO. [EDOHostService serviceWithPort:12345 - rootObject:[[EDOPlaceholder alloc] init] + rootObject:[[CPTestSharedObject alloc] init] queue:dispatch_get_main_queue()]; - [EDOHostNamingService.sharedService start]; - return YES; } @end -@implementation EDOPlaceholder +@implementation CPTestSharedObject - (NSString*)testEDO { return @"crashpad"; } + +- (void)crashBadAccess { + strcpy(0, "bla"); +} + +- (void)crashKillAbort { + kill(getpid(), SIGABRT); +} + +- (void)crashSegv { + long zero = 0; + *(long*)zero = 0xC045004d; +} + +- (void)crashTrap { + __builtin_trap(); +} + +- (void)crashAbort { + abort(); +} + @end diff --git a/test/ios/host/edo_placeholder.h b/test/ios/host/cptest_shared_object.h similarity index 54% rename from test/ios/host/edo_placeholder.h rename to test/ios/host/cptest_shared_object.h index 84bf0c44..483fce47 100644 --- a/test/ios/host/edo_placeholder.h +++ b/test/ios/host/cptest_shared_object.h @@ -12,13 +12,29 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef CRASHPAD_TEST_IOS_HOST_EDO_PLACEHOLDER_H_ -#define CRASHPAD_TEST_IOS_HOST_EDO_PLACEHOLDER_H_ +#ifndef CRASHPAD_TEST_IOS_HOST_SHARED_OBJECT_H_ +#define CRASHPAD_TEST_IOS_HOST_SHARED_OBJECT_H_ #import -@interface EDOPlaceholder : NSObject +@interface CPTestSharedObject : NSObject +// Returns the string "crashpad" for testing EDO. - (NSString*)testEDO; + +// Triggers an EXC_BAD_ACCESS exception and crash. +- (void)crashBadAccess; + +// Triggers a crash with a call to kill(SIGABRT). +- (void)crashKillAbort; + +// Triggers a segfault crash. +- (void)crashSegv; + +// Trigger a crash with a __builtin_trap. +- (void)crashTrap; + +// Trigger a crash with an abort(). +- (void)crashAbort; @end -#endif // CRASHPAD_TEST_IOS_HOST_EDO_PLACEHOLDER_H_ +#endif // CRASHPAD_TEST_IOS_HOST_SHARED_OBJECT_H_ diff --git a/tools/BUILD.gn b/tools/BUILD.gn index 41f0fb69..5e0228b9 100644 --- a/tools/BUILD.gn +++ b/tools/BUILD.gn @@ -25,29 +25,31 @@ source_set("tool_support") { deps = [ "../third_party/mini_chromium:base" ] } -crashpad_executable("crashpad_database_util") { - sources = [ "crashpad_database_util.cc" ] +if (!crashpad_is_ios) { + crashpad_executable("crashpad_database_util") { + sources = [ "crashpad_database_util.cc" ] - deps = [ - ":tool_support", - "../build:default_exe_manifest_win", - "../client", - "../compat", - "../third_party/mini_chromium:base", - "../util", - ] -} + deps = [ + ":tool_support", + "../build:default_exe_manifest_win", + "../client", + "../compat", + "../third_party/mini_chromium:base", + "../util", + ] + } -crashpad_executable("crashpad_http_upload") { - sources = [ "crashpad_http_upload.cc" ] + crashpad_executable("crashpad_http_upload") { + sources = [ "crashpad_http_upload.cc" ] - deps = [ - ":tool_support", - "../build:default_exe_manifest_win", - "../compat", - "../third_party/mini_chromium:base", - "../util", - ] + deps = [ + ":tool_support", + "../build:default_exe_manifest_win", + "../compat", + "../third_party/mini_chromium:base", + "../util", + ] + } } crashpad_executable("base94_encoder") { @@ -60,7 +62,7 @@ crashpad_executable("base94_encoder") { ] } -if (!crashpad_is_fuchsia) { +if (!crashpad_is_fuchsia && !crashpad_is_ios) { crashpad_executable("generate_dump") { sources = [ "generate_dump.cc" ] @@ -88,7 +90,8 @@ if (!crashpad_is_fuchsia) { } if (crashpad_is_win) { - cflags = [ "/wd4201" ] # nonstandard extension used : nameless struct/union + cflags = + [ "/wd4201" ] # nonstandard extension used : nameless struct/union } } } diff --git a/util/stdlib/strnlen.cc b/util/stdlib/strnlen.cc index a238728f..7ef8d3b1 100644 --- a/util/stdlib/strnlen.cc +++ b/util/stdlib/strnlen.cc @@ -14,7 +14,8 @@ #include "util/stdlib/strnlen.h" -#if defined(OS_MACOSX) && MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_7 +#if defined(OS_MACOSX) && !defined(OS_IOS) && \ + MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_7 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7 // Redeclare a method only available on Mac OS X 10.7 and later to suppress a diff --git a/util/stdlib/strnlen.h b/util/stdlib/strnlen.h index e85d8c7e..1db5f6e2 100644 --- a/util/stdlib/strnlen.h +++ b/util/stdlib/strnlen.h @@ -20,7 +20,7 @@ #include "build/build_config.h" -#if defined(OS_MACOSX) +#if defined(OS_MACOSX) && !defined(OS_IOS) #include #endif @@ -38,7 +38,7 @@ namespace crashpad { //! and not all systems’ standard libraries provide an implementation. size_t strnlen(const char* string, size_t max_length); -#if !defined(OS_MACOSX) || \ +#if !defined(OS_MACOSX) || defined(OS_IOS) || \ MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_7 inline size_t strnlen(const char* string, size_t max_length) { return ::strnlen(string, max_length);