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 { ...@@ -437,12 +437,12 @@ class IOSDevice extends Device {
final Uri? localUri = await observatoryDiscovery?.uri; final Uri? localUri = await observatoryDiscovery?.uri;
timer.cancel(); timer.cancel();
if (localUri == null) { if (localUri == null) {
iosDeployDebugger?.detach(); await iosDeployDebugger?.stopAndDumpBacktrace();
return LaunchResult.failed(); return LaunchResult.failed();
} }
return LaunchResult.succeeded(observatoryUri: localUri); return LaunchResult.succeeded(observatoryUri: localUri);
} on ProcessException catch (e) { } on ProcessException catch (e) {
iosDeployDebugger?.detach(); await iosDeployDebugger?.stopAndDumpBacktrace();
_logger.printError(e.message); _logger.printError(e.message);
return LaunchResult.failed(); return LaunchResult.failed();
} finally { } finally {
......
...@@ -293,6 +293,15 @@ class IOSDeployDebugger { ...@@ -293,6 +293,15 @@ class IOSDeployDebugger {
// (lldb) Process 6152 stopped // (lldb) Process 6152 stopped
static final RegExp _lldbProcessStopped = RegExp(r'Process \d* 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. /// Launch the app on the device, and attach the debugger.
/// ///
/// Returns whether or not the debugger successfully attached. /// Returns whether or not the debugger successfully attached.
...@@ -330,16 +339,41 @@ class IOSDeployDebugger { ...@@ -330,16 +339,41 @@ class IOSDeployDebugger {
} }
return; return;
} }
if (line.contains('PROCESS_STOPPED') || if (line == _signalStop) {
line.contains('PROCESS_EXITED') || // The app is about to be stopped. Only show in verbose mode.
_lldbProcessExit.hasMatch(line) || _logger.printTrace(line);
_lldbProcessStopped.hasMatch(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 // The app exited or crashed, so exit. Continue passing debugging
// messages to the log reader until it exits to capture crash dumps. // messages to the log reader until it exits to capture crash dumps.
_logger.printTrace(line); _logger.printTrace(line);
exit(); exit();
return; 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) { if (_debuggerState != _IOSDeployDebuggerState.attached) {
_logger.printTrace(line); _logger.printTrace(line);
return; return;
...@@ -395,6 +429,22 @@ class IOSDeployDebugger { ...@@ -395,6 +429,22 @@ class IOSDeployDebugger {
return success; 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() { void detach() {
if (!debuggerAttached) { if (!debuggerAttached) {
return; return;
...@@ -403,7 +453,6 @@ class IOSDeployDebugger { ...@@ -403,7 +453,6 @@ class IOSDeployDebugger {
try { try {
// Detach lldb from the app process. // Detach lldb from the app process.
_iosDeployProcess?.stdin.writeln('process detach'); _iosDeployProcess?.stdin.writeln('process detach');
_debuggerState = _IOSDeployDebuggerState.detached;
} on SocketException catch (error) { } on SocketException catch (error) {
// Best effort, try to detach, but maybe the app already exited or already detached. // Best effort, try to detach, but maybe the app already exited or already detached.
_logger.printTrace('Could not detach from debugger: $error'); _logger.printTrace('Could not detach from debugger: $error');
......
...@@ -90,11 +90,14 @@ void main () { ...@@ -90,11 +90,14 @@ void main () {
logger = BufferLogger.test(); 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>[ final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand( FakeCommand(
command: <String>['ios-deploy'], 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 exit', 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( final IOSDeployDebugger iosDeployDebugger = IOSDeployDebugger.test(
...@@ -113,7 +116,15 @@ void main () { ...@@ -113,7 +116,15 @@ void main () {
'Log on attach2', '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 () { ...@@ -141,11 +152,14 @@ void main () {
}); });
testWithoutContext('app crash', () async { 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>[ final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand( FakeCommand(
command: <String>['ios-deploy'], command: const <String>['ios-deploy'],
stdout: 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( final IOSDeployDebugger iosDeployDebugger = IOSDeployDebugger.test(
...@@ -162,6 +176,14 @@ void main () { ...@@ -162,6 +176,14 @@ void main () {
'Log on attach', 'Log on attach',
'* thread #1, stop reason = Assertion failed:', '* 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 { testWithoutContext('attach failed', () async {
...@@ -268,6 +290,31 @@ void main () { ...@@ -268,6 +290,31 @@ void main () {
iosDeployDebugger.detach(); iosDeployDebugger.detach();
expect(await stdinStream.first, 'process 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', () { 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