Have runAsyncChecked throw a ProcessException instead of a String. (#22710)

diff --git a/packages/flutter_tools/lib/src/base/process.dart b/packages/flutter_tools/lib/src/base/process.dart
index c612e45..5632752 100644
--- a/packages/flutter_tools/lib/src/base/process.dart
+++ b/packages/flutter_tools/lib/src/base/process.dart
@@ -247,8 +247,10 @@
     allowReentrantFlutter: allowReentrantFlutter,
     environment: environment,
   );
-  if (result.exitCode != 0)
-    throw 'Exit code ${result.exitCode} from: ${cmd.join(' ')}:\n$result';
+  if (result.exitCode != 0) {
+    throw ProcessException(cmd[0], cmd.sublist(1),
+      'Process "${cmd[0]}" exited abnormally:\n$result', result.exitCode);
+  }
   return result;
 }
 
diff --git a/packages/flutter_tools/lib/src/ios/simulators.dart b/packages/flutter_tools/lib/src/ios/simulators.dart
index 587d6c6..207e822 100644
--- a/packages/flutter_tools/lib/src/ios/simulators.dart
+++ b/packages/flutter_tools/lib/src/ios/simulators.dart
@@ -120,22 +120,44 @@
   }
 
   Future<RunResult> install(String deviceId, String appPath) {
-    return runCheckedAsync(<String>[_xcrunPath, 'simctl', 'install', deviceId, appPath]);
+    Future<RunResult> result;
+    try {
+      result = runCheckedAsync(<String>[_xcrunPath, 'simctl', 'install', deviceId, appPath]);
+    } on ProcessException catch (exception) {
+      throwToolExit('Unable to install $appPath on $deviceId:\n$exception');
+    }
+    return result;
   }
 
   Future<RunResult> uninstall(String deviceId, String appId) {
-    return runCheckedAsync(<String>[_xcrunPath, 'simctl', 'uninstall', deviceId, appId]);
+    Future<RunResult> result;
+    try {
+      result = runCheckedAsync(<String>[_xcrunPath, 'simctl', 'uninstall', deviceId, appId]);
+    } on ProcessException catch (exception) {
+      throwToolExit('Unable to uninstall $appId from $deviceId:\n$exception');
+    }
+    return result;
   }
 
   Future<RunResult> launch(String deviceId, String appIdentifier, [List<String> launchArgs]) {
     final List<String> args = <String>[_xcrunPath, 'simctl', 'launch', deviceId, appIdentifier];
     if (launchArgs != null)
       args.addAll(launchArgs);
-    return runCheckedAsync(args);
+    Future<RunResult> result;
+    try {
+      result = runCheckedAsync(args);
+    } on ProcessException catch (exception) {
+      throwToolExit('Unable to launch $appIdentifier on $deviceId:\n$exception');
+    }
+    return result;
   }
 
   Future<void> takeScreenshot(String deviceId, String outputPath) async {
-    await runCheckedAsync(<String>[_xcrunPath, 'simctl', 'io', deviceId, 'screenshot', outputPath]);
+    try {
+      await runCheckedAsync(<String>[_xcrunPath, 'simctl', 'io', deviceId, 'screenshot', outputPath]);
+    } on ProcessException catch (exception) {
+      throwToolExit('Unable to take screenshot of $deviceId:\n$exception');
+    }
   }
 }
 
diff --git a/packages/flutter_tools/test/base/process_test.dart b/packages/flutter_tools/test/base/process_test.dart
index 5f4aa4d..1281a92 100644
--- a/packages/flutter_tools/test/base/process_test.dart
+++ b/packages/flutter_tools/test/base/process_test.dart
@@ -2,12 +2,28 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'package:flutter_tools/src/base/io.dart';
 import 'package:flutter_tools/src/base/process.dart';
+import 'package:mockito/mockito.dart';
+import 'package:process/process.dart';
 
 import '../src/common.dart';
 import '../src/context.dart';
 
 void main() {
+  group('process exceptions', () {
+    ProcessManager mockProcessManager;
+
+    setUp(() {
+      mockProcessManager = MockProcessManager();
+    });
+
+    testUsingContext('runCheckedAsync exceptions should be ProcessException objects', () async {
+      when(mockProcessManager.run(<String>['false']))
+          .thenAnswer((Invocation invocation) => Future<ProcessResult>.value(ProcessResult(0, 1, '', '')));
+      expect(() async => await runCheckedAsync(<String>['false']), throwsA(isInstanceOf<ProcessException>()));
+    }, overrides: <Type, Generator>{ProcessManager: () => mockProcessManager});
+  });
   group('shutdownHooks', () {
     testUsingContext('runInExpectedOrder', () async {
       int i = 1;
@@ -41,3 +57,5 @@
     });
   });
 }
+
+class MockProcessManager extends Mock implements ProcessManager {}