Subversion Repositories Mobile Apps.GyroMouse

Rev

Blame | Last modification | View Log | Download | RSS feed

  1. // rdc.m -- standard error and crash backtrace remote logging for iOS by Pierre-Marie Baty <pm@pmbaty.com>
  2. //          part of iOS Build Environment -- https://www.pmbaty.com/iosbuildenv/
  3.  
  4. #define REMOTE_CONSOLE "192.168.1.136"
  5.  
  6. #ifdef REMOTE_CONSOLE
  7.  
  8. // Objective-C includes
  9. #import <UIKit/UIKit.h>
  10.  
  11. // standard C includes
  12. #include <stdio.h> // required for fprintf()
  13. #include <string.h> // required for memset() and strerror()
  14. #include <signal.h> // required for signal(), raise() and signal IDs
  15. #include <sys/time.h> // required before including sys/select.h
  16. #include <sys/socket.h> // required for socket() and connect()
  17. #include <sys/select.h> // required for select()
  18. #include <fcntl.h> // for fcntl()
  19. #include <arpa/inet.h> // required for htons() and inet_addr()
  20. #include <execinfo.h> // required for backtrace access
  21. #include <unistd.h> // required for dup2()
  22. #include <dispatch/dispatch.h> // required for GCD
  23. #include <dlfcn.h> // for dlsym()
  24. #include <errno.h> // for errno
  25. #include <objc/runtime.h> // for objc_getClass()
  26.  
  27.  
  28. // forward declaration of __cxa_demangle() for C. Normally in <cxxabi.h> which is only accessible when compiling C++ code.
  29. // since we only need this single function, don't bother including the whole header as it causes errors when the implicit include paths change.
  30. extern char *__cxa_demangle (const char *mangled_name, char *output_buffer, size_t *length, int *status);
  31.  
  32.  
  33. // all this sh*t is necessary to display a detached messagebox in Objective-C. Don't you miss the MessageBoxA(NULL,"message","title",0) simplicity.
  34. @interface UIDetachedAlertController : UIAlertController
  35. @property (nonatomic, strong) UIWindow *w; // Apple say they create an invisible window with a true view controller to display detached messageboxes, so let's do so
  36. - (void) show; // this is where it will happen: create invisible window, create its root controller, bring them to front - and this will also bring up the attached UIAlertController
  37. @end
  38. @implementation UIDetachedAlertController
  39. - (void) viewDidLoad { [super viewDidLoad]; } // make sure the invisible window loads correctly
  40. - (void) viewDidDisappear: (BOOL) animated { [super viewDidDisappear: animated]; self.w.hidden = YES; self.w = nil; } // make sure the popup disappears when the invisible window does so
  41. - (void) show { self.w = [[UIWindow alloc] initWithFrame: [[UIScreen mainScreen] bounds]]; self.w.rootViewController = [[UIViewController alloc] init]; self.w.windowLevel = UIWindowLevelAlert + 1; [self.w makeKeyAndVisible]; [self.w.rootViewController presentViewController: self animated: YES completion: nil]; }
  42. @end
  43. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  44. // in order to display a graphical alert, Cocoa must be started. Since Cocoa is not started yet when we run, delay 1 second before doing the job
  45. #define alert(msg) dispatch_after (dispatch_time (DISPATCH_TIME_NOW, NSEC_PER_SEC), dispatch_get_main_queue (), ^{ \
  46.     if ([UIApplication sharedApplication] != NULL) /* is this a graphical app? note we can only test this after Cocoa has started */ { \
  47.         if (objc_getClass ("UIAlertController") != NULL) /* does it support new style alert views? */ { \
  48.             UIDetachedAlertController *alert_controller = [UIDetachedAlertController alertControllerWithTitle: @"Warning" message: @(msg) preferredStyle: UIAlertControllerStyleAlert]; \
  49.             [alert_controller addAction: [UIAlertAction actionWithTitle: @"OK" style: UIAlertActionStyleDefault handler: nil]]; \
  50.             [alert_controller show]; \
  51.         } else /* it wants old style alert views */ [[[UIAlertView alloc] initWithTitle: @"Warning" message: @(msg) delegate: nil cancelButtonTitle: @"OK" otherButtonTitles: nil] show]; \
  52.     } else /* this is a console app */ fprintf (stdout, "Warning: %s\n", (msg)); \
  53. })
  54.  
  55.  
  56. // POSIX signal behaviours
  57. #define SIGNAL_DISCARD 0
  58. #define SIGNAL_TERMINATE 1
  59. #define SIGNAL_CRASHDUMP 2
  60. #define SIGNAL_PAUSE 3
  61.  
  62.  
  63. // POSIX signals description structure type definition
  64. typedef struct signal_s
  65. {
  66.    int is_hookable;
  67.    int posix_default;
  68.    sig_t prev_handler;
  69.    const char *name;
  70.    const char *desc;
  71. } signal_t;
  72.  
  73.  
  74. // array of signal descriptions (MUST BE ALIGNED WITH signal.h VALUES!)
  75. static signal_t signals[] =
  76. {
  77.    { 0, SIGNAL_DISCARD,   NULL, NULL,     NULL },
  78.    { 1, SIGNAL_TERMINATE, NULL, "HUP",    "terminal line hangup" },
  79.    { 1, SIGNAL_TERMINATE, NULL, "INT",    "interrupt program" },
  80.    { 1, SIGNAL_CRASHDUMP, NULL, "QUIT",   "quit program" },
  81.    { 1, SIGNAL_CRASHDUMP, NULL, "ILL",    "illegal instruction" },
  82.    { 1, SIGNAL_CRASHDUMP, NULL, "TRAP",   "trace trap" },
  83.    { 1, SIGNAL_CRASHDUMP, NULL, "ABRT",   "abort() call" },
  84.    { 1, SIGNAL_CRASHDUMP, NULL, "EMT",    "emulate instruction executed" },
  85.    { 1, SIGNAL_CRASHDUMP, NULL, "FPE",    "floating-point exception" },
  86.    { 0, SIGNAL_TERMINATE, NULL, "KILL",   "kill program" }, // cannot be caught!
  87.    { 1, SIGNAL_CRASHDUMP, NULL, "BUS",    "bus error" },
  88.    { 1, SIGNAL_CRASHDUMP, NULL, "SEGV",   "segmentation violation" },
  89.    { 1, SIGNAL_CRASHDUMP, NULL, "SYS",    "non-existent system call invoked" },
  90.    { 1, SIGNAL_TERMINATE, NULL, "PIPE",   "write on a pipe with no reader" },
  91.    { 1, SIGNAL_TERMINATE, NULL, "ALRM",   "real-time timer expired" },
  92.    { 1, SIGNAL_TERMINATE, NULL, "TERM",   "software termination signal" },
  93.    { 1, SIGNAL_DISCARD,   NULL, "URG",    "urgent condition present on socket" },
  94.    { 0, SIGNAL_PAUSE,     NULL, "STOP",   "stop" }, // cannot be caught!
  95.    { 1, SIGNAL_PAUSE,     NULL, "TSTP",   "stop signal generated from keyboard" },
  96.    { 1, SIGNAL_DISCARD,   NULL, "CONT",   "continue after stop" },
  97.    { 1, SIGNAL_DISCARD,   NULL, "CHLD",   "child status has changed" },
  98.    { 1, SIGNAL_PAUSE,     NULL, "TTIN",   "background read attempted from control terminal" },
  99.    { 1, SIGNAL_PAUSE,     NULL, "TTOU",   "background write attempted to control terminal" },
  100.    { 1, SIGNAL_DISCARD,   NULL, "IO",     "I/O is possible on a descriptor" },
  101.    { 1, SIGNAL_TERMINATE, NULL, "XCPU",   "CPU time limit exceeded" },
  102.    { 1, SIGNAL_TERMINATE, NULL, "XFSZ",   "file size limit exceeded" },
  103.    { 1, SIGNAL_TERMINATE, NULL, "VTALRM", "virtual time alarm" },
  104.    { 1, SIGNAL_TERMINATE, NULL, "PROF",   "profiling timer alarm" },
  105.    { 1, SIGNAL_DISCARD,   NULL, "WINCH",  "window size change" },
  106.    { 1, SIGNAL_DISCARD,   NULL, "INFO",   "status request from keyboard" },
  107.    { 1, SIGNAL_TERMINATE, NULL, "USR1",   "user-defined signal 1" },
  108.    { 1, SIGNAL_TERMINATE, NULL, "USR2",   "user-defined signal 2" },
  109. };
  110. static signal_t *last_sig = NULL;
  111. static int is_repeating = 0;
  112.  
  113.  
  114. static void __exit_handler (void)
  115. {
  116.    // handy function to notify the user on the remote debug console that the
  117.    // process is exiting by return of main() or by a call to exit().
  118.    
  119.    fprintf (stderr, "Your app exited by its own will.\n");
  120.    return;
  121. }
  122.  
  123.  
  124. static void __sig_handler (int signal_id)
  125. {
  126.    // this very handy function dumps the stack trace to the standard error stream
  127.    // in case the program crashes. Very useful with the remote debug console!
  128.  
  129.    void *callstack[128];
  130.    char **backtrace_strings;
  131.    int backtrace_count;
  132.    int backtrace_index;
  133.    int prev_errno;
  134.    signal_t *sig;
  135.    char *func;
  136.    char *offs;
  137.  
  138.    prev_errno = errno; // have a copy of errno to keep it safe
  139.  
  140.    // is it one of the signals we know ?
  141.    sig = NULL;
  142.    if ((signal_id > 1) && (signal_id < (int) (sizeof (signals) / sizeof (signal_t))))
  143.    {
  144.       sig = &signals[signal_id]; // quick access to signal
  145.       if (sig != last_sig)
  146.       {
  147.          is_repeating = 0;
  148.          if (sig->posix_default == SIGNAL_CRASHDUMP)
  149.          {
  150.             fprintf (stderr, "SIG%s received (%s) -- WHOA! I did something nasty. Dumping the stack trace:\n", sig->name, sig->desc);
  151.             backtrace_count = backtrace (callstack, sizeof (callstack) / sizeof (callstack[0]));
  152.             backtrace_strings = backtrace_symbols (callstack, backtrace_count);
  153.             for (backtrace_index = 0; backtrace_index < backtrace_count; backtrace_index++)
  154.             {
  155.                func = strstr (backtrace_strings[backtrace_index], " _Z"); // is there a C++ mangled name in this string ?
  156.                if (func != NULL)
  157.                {
  158.                   *func = 0; // break the string here
  159.                   func++; // jump over it
  160.                   offs = strstr (func, " + ");
  161.                   if (offs != NULL)
  162.                   {
  163.                      *offs = 0;
  164.                      offs++;
  165.                      fprintf (stderr, "\t%s %s %s\n", backtrace_strings[backtrace_index], __cxa_demangle (func, NULL, NULL, NULL), offs);
  166.                   }
  167.                   else
  168.                      fprintf (stderr, "\t%s\n", backtrace_strings[backtrace_index]);
  169.                }
  170.                else
  171.                   fprintf (stderr, "\t%s\n", backtrace_strings[backtrace_index]);
  172.             }
  173.             free (backtrace_strings);
  174.          }
  175.          else
  176.             fprintf (stderr, "SIG%s received (%s)\n", sig->name, sig->desc);
  177.       }
  178.       else
  179.       {
  180.          if (is_repeating == 0)
  181.             fprintf (stderr, "SIG%s repeating (%s) [future occurences will be ignored]\n", sig->name, sig->desc);
  182.          is_repeating = 1;
  183.       }
  184.       last_sig = sig; // remember last received signal to avoid excessive flooding
  185.    }
  186.    else
  187.       fprintf (stderr, "Signal %d received (unknown signal)\n", signal_id);
  188.  
  189.    // should this signal be ignored ?
  190.    if (sig->prev_handler == SIG_IGN)
  191.    {
  192.       errno = prev_errno; // restore errno
  193.       return; // and ignore signal
  194.    }
  195.  
  196.    // else is it a user-defined signal handler ?
  197.    if ((sig->prev_handler != SIG_ERR) && (sig->prev_handler != SIG_DFL))
  198.    {
  199.       errno = prev_errno; // restore errno
  200.       sig->prev_handler (signal_id); // let the default behaviour happen (don't call raise() here)
  201.       return; // and return
  202.    }
  203.  
  204.    // else the expected behaviour must be SIG_DFL
  205.    signal (signal_id, SIG_DFL); // restore default signal handler
  206.    errno = prev_errno; // restore errno
  207.    kill (getpid (), signal_id); // call the default handler
  208.    return; // and return
  209. }
  210.  
  211.  
  212. int bootstrap_console (void)
  213. {
  214.    // and here's the new program entrypoint.
  215.    // Compile with -Dmain(argc,argv)=__attribute__((visibility("default")))_rdc_main(argc,argv) -DREMOTE_CONSOLE="xxx.xxx.xxx.xxx"
  216.    // and link with -exported_symbol *_rdc_main* to make sure the old entrypoint is exported by the linker and visible to dlsym().
  217.  
  218.    unsigned int sig_index;
  219.    int debug_socket[2];
  220.    int i;
  221.  
  222.    // redirect the standard output and standard error streams to a remote console
  223.    for (i = 0; i < 2; i++)
  224.       if ((debug_socket[i] = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP)) != -1)
  225.       {
  226.          struct sockaddr_in debugserver_sockaddr;
  227.          struct timeval timeout;
  228.          fd_set socket_set;
  229.  
  230.          memset (&debugserver_sockaddr, 0, sizeof (debugserver_sockaddr));
  231.          debugserver_sockaddr.sin_family = AF_INET;
  232.          debugserver_sockaddr.sin_addr.s_addr = inet_addr (REMOTE_CONSOLE);
  233.          debugserver_sockaddr.sin_port = htons (5001 + i); // console listens on TCP ports 5000 (legacy), 5001 (stdout) and 5002 (stderr)
  234.  
  235.          fcntl (debug_socket[i], F_SETFL, O_NONBLOCK); // set the socket into non-blocking mode
  236.          connect (debug_socket[i], (struct sockaddr *) &debugserver_sockaddr, sizeof (debugserver_sockaddr)); // connect to remote console
  237.  
  238.          FD_ZERO (&socket_set);
  239.          FD_SET (debug_socket[i], &socket_set);
  240.          timeout.tv_sec = 3; // 3 seconds timeout
  241.          timeout.tv_usec = 0;
  242.          if (select (debug_socket[i] + 1, NULL, &socket_set, NULL, &timeout) == 1)
  243.          {
  244.             int socket_error;
  245.             socklen_t socketerror_size = sizeof (socket_error);
  246.             getsockopt (debug_socket[i], SOL_SOCKET, SO_ERROR, &socket_error, &socketerror_size);
  247.             if (socket_error == 0)
  248.             {
  249.                fcntl (debug_socket[i], F_SETFL, fcntl (debug_socket[i], F_GETFL, 0) & ~O_NONBLOCK); // put the socket back into blocking mode
  250.                dup2 (debug_socket[i], (i == 0 ? STDOUT_FILENO : STDERR_FILENO)); // only do that if we can create the socket and connect to the remote console
  251.             }
  252.             else
  253.                alert ("Unable to connect to remote debug console at " REMOTE_CONSOLE " on ports 5001-5002.\n\nLogging will NOT be redirected.");
  254.          }
  255.          else
  256.             alert ("Timed out connecting remote debug console at " REMOTE_CONSOLE " on ports 5001-5002.\n\nLogging will NOT be redirected.\n\nMake sure the remote debug console application is open and that no firewall on your computer hinders its ability to listen to the network, then close and restart your app.");
  257.       }
  258.       else
  259.          alert ("Unable to create stdout/stderr socket to connect to remote debug console.\n\nLogging will NOT be redirected.");
  260.  
  261.    // the next thing to do is to trap signals with a custom handler to help debugging
  262.    for (sig_index = 0; sig_index < sizeof (signals) / sizeof (signal_t); sig_index++)
  263.       if (signals[sig_index].is_hookable)
  264.       {
  265.          signals[sig_index].prev_handler = signal (sig_index, __sig_handler); // hook every possible signal
  266.          if (signals[sig_index].prev_handler == SIG_ERR)
  267.             fprintf (stderr, "Warning: cannot register SIG%s handler function (errno %d: %s). You will not be notified if %s occurs.\n", signals[sig_index].name, errno, strerror (errno), signals[sig_index].desc);
  268.       }
  269.  
  270.    // now register a normal exit handler function to catch normal exits
  271.    if (atexit (__exit_handler) != 0)
  272.       fprintf (stderr, "Warning: cannot register normal exit handler function (errno %d: %s). You will not be notified if your app quits.\n", errno, strerror (errno));
  273.  
  274.    // I didn't know that it was impossible, so I did it.
  275.    fprintf (stderr, "Hello! I'm your app and this is my first message to stderr =)\n");
  276.    return (0);
  277. }
  278.  
  279. #endif // REMOTE_CONSOLE
  280.