Subversion Repositories Mobile Apps.GyroMouse

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
1 pmbaty 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