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 |