Merge pull request #304 from black-square/feature/detect_deadlocks-option

Added an option `--detect_deadlocks`
diff --git a/README.md b/README.md
index 77a6899..6ad87c9 100644
--- a/README.md
+++ b/README.md
@@ -92,6 +92,7 @@
         -e, --exists                 check if the app with given bundle_id is installed or not 
         -B, --list_bundle_id         list bundle_id 
         -W, --no-wifi                ignore wifi devices
+        --detect_deadlocks <sec>     start printing backtraces for all threads periodically after specific amount of seconds
 
 ## Examples
 
@@ -142,3 +143,6 @@
 
 * `make demo.app` will generate the demo.app executable. If it doesn't compile, modify `IOS_SDK_VERSION` in the Makefile.
 * `make debug` will install demo.app and launch a LLDB session.
+
+## Notes
+* `--detect_deadlocks` can help to identify an exact state of application's threads in case of a deadlock. It works like this: The user specifies the amount of time ios-deploy runs the app as usual. When the timeout is elapsed ios-deploy starts to print call-stacks of all threads every 5 seconds and the app keeps running. Comparing threads' call-stacks between each other helps to identify the threads which were stuck.
diff --git a/src/ios-deploy/ios-deploy.m b/src/ios-deploy/ios-deploy.m
index c890eea..deda5cc 100644
--- a/src/ios-deploy/ios-deploy.m
+++ b/src/ios-deploy/ios-deploy.m
@@ -88,6 +88,7 @@
 char *args = NULL;
 char *list_root = NULL;
 int _timeout = 0;
+int _detectDeadlockTimeout = 0;
 int port = 0;    // 0 means "dynamically assigned"
 CFStringRef last_path = NULL;
 service_conn_t gdbfd;
@@ -615,9 +616,14 @@
     CFMutableStringRef pmodule = CFStringCreateMutableCopy(NULL, 0, (CFStringRef)LLDB_FRUITSTRAP_MODULE);
 
     CFRange rangeLLDB = { 0, CFStringGetLength(pmodule) };
+    
     CFStringRef exitcode_app_crash_str = CFStringCreateWithFormat(NULL, NULL, CFSTR("%d"), exitcode_app_crash);
     CFStringFindAndReplace(pmodule, CFSTR("{exitcode_app_crash}"), exitcode_app_crash_str, rangeLLDB, 0);
     rangeLLDB.length = CFStringGetLength(pmodule);
+    
+    CFStringRef detect_deadlock_timeout_str = CFStringCreateWithFormat(NULL, NULL, CFSTR("%d"), _detectDeadlockTimeout);
+    CFStringFindAndReplace(pmodule, CFSTR("{detect_deadlock_timeout}"), detect_deadlock_timeout_str, rangeLLDB, 0);
+    rangeLLDB.length = CFStringGetLength(pmodule);
 
     if (args) {
         CFStringRef cf_args = CFStringCreateWithCString(NULL, args, kCFStringEncodingUTF8);
@@ -1707,7 +1713,8 @@
         @"  -V, --version                print the executable version \n"
         @"  -e, --exists                 check if the app with given bundle_id is installed or not \n"
         @"  -B, --list_bundle_id         list bundle_id \n"
-        @"  -W, --no-wifi                ignore wifi devices\n",
+        @"  -W, --no-wifi                ignore wifi devices\n"
+        @"  --detect_deadlocks <sec>     start printing backtraces for all threads periodically after specific amount of seconds\n",
         [NSString stringWithUTF8String:app]);
 }
 
@@ -1752,9 +1759,10 @@
         { "exists", no_argument, NULL, 'e'},
         { "list_bundle_id", no_argument, NULL, 'B'},
         { "no-wifi", no_argument, NULL, 'W'},
+        { "detect_deadlocks", required_argument, NULL, 1000 },
         { NULL, 0, NULL, 0 },
     };
-    char ch;
+    int ch;
 
     while ((ch = getopt_long(argc, argv, "VmcdvunrILeD:R:i:b:a:t:g:x:p:1:2:o:l::w::9::B::W", longopts, NULL)) != -1)
     {
@@ -1855,6 +1863,9 @@
         case 'W':
             no_wifi = true;
             break;
+        case 1000:
+            _detectDeadlockTimeout = atoi(optarg);
+            break;
         default:
             usage(argv[0]);
             return exitcode_error;
diff --git a/src/scripts/lldb.py b/src/scripts/lldb.py
index de297d0..3e149aa 100644
--- a/src/scripts/lldb.py
+++ b/src/scripts/lldb.py
@@ -1,7 +1,8 @@
-import lldb
+import time
 import os
 import sys
 import shlex
+import lldb
 
 listener = None
 
@@ -78,6 +79,9 @@
 def autoexit_command(debugger, command, result, internal_dict):
     global listener
     process = lldb.target.process
+
+    detectDeadlockTimeout = {detect_deadlock_timeout}
+    printBacktraceTime = time.time() + detectDeadlockTimeout if detectDeadlockTimeout > 0 else None
     
     # This line prevents internal lldb listener from processing STDOUT/STDERR messages. Without it, an order of log writes is incorrect sometimes
     debugger.GetListener().StopListeningForEvents(process.GetBroadcaster(), lldb.SBProcess.eBroadcastBitSTDOUT | lldb.SBProcess.eBroadcastBitSTDERR )
@@ -118,7 +122,7 @@
         if state == lldb.eStateExited:
             sys.stdout.write( '\\nPROCESS_EXITED\\n' )
             os._exit(process.GetExitStatus())
-        elif state == lldb.eStateStopped:
+        elif printBacktraceTime is None and state == lldb.eStateStopped:
             sys.stdout.write( '\\nPROCESS_STOPPED\\n' )
             debugger.HandleCommand('bt')
             os._exit({exitcode_app_crash})
@@ -129,4 +133,10 @@
         elif state == lldb.eStateDetached:
             sys.stdout.write( '\\nPROCESS_DETACHED\\n' )
             os._exit({exitcode_app_crash})
-
+        elif printBacktraceTime is not None and time.time() >= printBacktraceTime:
+            printBacktraceTime = None
+            sys.stdout.write( '\\nPRINT_BACKTRACE_TIMEOUT\\n' )
+            debugger.HandleCommand('process interrupt')
+            debugger.HandleCommand('bt all')
+            debugger.HandleCommand('continue')
+            printBacktraceTime = time.time() + 5