Unverified Commit cef68236 authored by Jenn Magder's avatar Jenn Magder Committed by GitHub

Dump backtrace when cannot attach to observatory (#98550)

parent 58ad6e1b
......@@ -437,12 +437,12 @@ class IOSDevice extends Device {
final Uri? localUri = await observatoryDiscovery?.uri;
timer.cancel();
if (localUri == null) {
iosDeployDebugger?.detach();
await iosDeployDebugger?.stopAndDumpBacktrace();
return LaunchResult.failed();
}
return LaunchResult.succeeded(observatoryUri: localUri);
} on ProcessException catch (e) {
iosDeployDebugger?.detach();
await iosDeployDebugger?.stopAndDumpBacktrace();
_logger.printError(e.message);
return LaunchResult.failed();
} finally {
......
......@@ -293,6 +293,15 @@ class IOSDeployDebugger {
// (lldb) Process 6152 stopped
static final RegExp _lldbProcessStopped = RegExp(r'Process \d* stopped');
// (lldb) Process 6152 detached
static final RegExp _lldbProcessDetached = RegExp(r'Process \d* detached');
// Send signal to stop (pause) the app. Used before a backtrace dump.
static const String _signalStop = 'process signal SIGSTOP';
// Print backtrace for all threads while app is stopped.
static const String _backTraceAll = 'thread backtrace all';
/// Launch the app on the device, and attach the debugger.
///
/// Returns whether or not the debugger successfully attached.
......@@ -330,16 +339,41 @@ class IOSDeployDebugger {
}
return;
}
if (line.contains('PROCESS_STOPPED') ||
line.contains('PROCESS_EXITED') ||
_lldbProcessExit.hasMatch(line) ||
_lldbProcessStopped.hasMatch(line)) {
if (line == _signalStop) {
// The app is about to be stopped. Only show in verbose mode.
_logger.printTrace(line);
return;
}
if (line == _backTraceAll) {
// The app is stopped and the backtrace for all threads will be printed.
_logger.printTrace(line);
// Even though we're not "detached", just stopped, mark as detached so the backtrace
// is only show in verbose.
_debuggerState = _IOSDeployDebuggerState.detached;
return;
}
if (line.contains('PROCESS_STOPPED') || _lldbProcessStopped.hasMatch(line)) {
// The app has been stopped. Dump the backtrace, and detach.
_logger.printTrace(line);
_iosDeployProcess?.stdin.writeln(_backTraceAll);
detach();
return;
}
if (line.contains('PROCESS_EXITED') || _lldbProcessExit.hasMatch(line)) {
// The app exited or crashed, so exit. Continue passing debugging
// messages to the log reader until it exits to capture crash dumps.
_logger.printTrace(line);
exit();
return;
}
if (_lldbProcessDetached.hasMatch(line)) {
// The debugger has detached from the app, and there will be no more debugging messages.
// Kill the ios-deploy process.
exit();
return;
}
if (_debuggerState != _IOSDeployDebuggerState.attached) {
_logger.printTrace(line);
return;
......@@ -395,6 +429,22 @@ class IOSDeployDebugger {
return success;
}
Future<void> stopAndDumpBacktrace() async {
if (!debuggerAttached) {
return;
}
try {
// Stop the app, which will prompt the backtrace to be printed for all threads in the stdoutSubscription handler.
_iosDeployProcess?.stdin.writeln(_signalStop);
} on SocketException catch (error) {
// Best effort, try to detach, but maybe the app already exited or already detached.
_logger.printTrace('Could not stop app from debugger: $error');
}
// Wait for logging to finish on process exit.
return logLines.drain();
}
void detach() {
if (!debuggerAttached) {
return;
......@@ -403,7 +453,6 @@ class IOSDeployDebugger {
try {
// Detach lldb from the app process.
_iosDeployProcess?.stdin.writeln('process detach');
_debuggerState = _IOSDeployDebuggerState.detached;
} on SocketException catch (error) {
// Best effort, try to detach, but maybe the app already exited or already detached.
_logger.printTrace('Could not detach from debugger: $error');
......
......@@ -90,11 +90,14 @@ void main () {
logger = BufferLogger.test();
});
testWithoutContext('debugger attached', () async {
testWithoutContext('debugger attached and stopped', () async {
final StreamController<List<int>> stdin = StreamController<List<int>>();
final Stream<String> stdinStream = stdin.stream.transform<String>(const Utf8Decoder());
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>['ios-deploy'],
stdout: '(lldb) run\r\nsuccess\r\nsuccess\r\nLog on attach1\r\n\r\nLog on attach2\r\n\r\n\r\n\r\nPROCESS_STOPPED\r\nLog after process exit',
FakeCommand(
command: const <String>['ios-deploy'],
stdout: "(lldb) run\r\nsuccess\r\nsuccess\r\nLog on attach1\r\n\r\nLog on attach2\r\n\r\n\r\n\r\nPROCESS_STOPPED\r\nLog after process stop\r\nthread backtrace all\r\n* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP",
stdin: IOSink(stdin.sink),
),
]);
final IOSDeployDebugger iosDeployDebugger = IOSDeployDebugger.test(
......@@ -113,7 +116,15 @@ void main () {
'Log on attach2',
'',
'',
'Log after process exit',
'Log after process stop'
]);
expect(logger.traceText, contains('PROCESS_STOPPED'));
expect(logger.traceText, contains('thread backtrace all'));
expect(logger.traceText, contains('* thread #1'));
expect(await stdinStream.take(3).toList(), <String>[
'thread backtrace all',
'\n',
'process detach',
]);
});
......@@ -141,11 +152,14 @@ void main () {
});
testWithoutContext('app crash', () async {
final StreamController<List<int>> stdin = StreamController<List<int>>();
final Stream<String> stdinStream = stdin.stream.transform<String>(const Utf8Decoder());
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>['ios-deploy'],
FakeCommand(
command: const <String>['ios-deploy'],
stdout:
'(lldb) run\r\nsuccess\r\nLog on attach\r\n(lldb) Process 6156 stopped\r\n* thread #1, stop reason = Assertion failed:',
'(lldb) run\r\nsuccess\r\nLog on attach\r\n(lldb) Process 6156 stopped\r\n* thread #1, stop reason = Assertion failed:\r\nthread backtrace all\r\n* thread #1, stop reason = Assertion failed:',
stdin: IOSink(stdin.sink),
),
]);
final IOSDeployDebugger iosDeployDebugger = IOSDeployDebugger.test(
......@@ -162,6 +176,14 @@ void main () {
'Log on attach',
'* thread #1, stop reason = Assertion failed:',
]);
expect(logger.traceText, contains('Process 6156 stopped'));
expect(logger.traceText, contains('thread backtrace all'));
expect(logger.traceText, contains('* thread #1'));
expect(await stdinStream.take(3).toList(), <String>[
'thread backtrace all',
'\n',
'process detach',
]);
});
testWithoutContext('attach failed', () async {
......@@ -268,6 +290,31 @@ void main () {
iosDeployDebugger.detach();
expect(await stdinStream.first, 'process detach');
});
testWithoutContext('stop with backtrace', () async {
final StreamController<List<int>> stdin = StreamController<List<int>>();
final Stream<String> stdinStream = stdin.stream.transform<String>(const Utf8Decoder());
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
FakeCommand(
command: const <String>[
'ios-deploy',
],
stdout:
'(lldb) run\nsuccess\nLog on attach\n(lldb) Process 6156 stopped\n* thread #1, stop reason = Assertion failed:\n(lldb) Process 6156 detached',
stdin: IOSink(stdin.sink),
),
]);
final IOSDeployDebugger iosDeployDebugger = IOSDeployDebugger.test(
processManager: processManager,
);
await iosDeployDebugger.launchAndAttach();
await iosDeployDebugger.stopAndDumpBacktrace();
expect(await stdinStream.take(3).toList(), <String>[
'thread backtrace all',
'\n',
'process detach',
]);
});
});
group('IOSDeploy.uninstallApp', () {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment