// rdc.
m -- standard
error and crash backtrace remote logging
for iOS by Pierre-Marie Baty <pm@pmbaty.
com>
// part of iOS Build Environment -- https://www.pmbaty.com/iosbuildenv/
#define REMOTE_CONSOLE "192.168.1.136"
#ifdef REMOTE_CONSOLE
// Objective-C includes
#import <UIKit/UIKit.h>
// standard C includes
#include <stdio.
h> // required
for fprintf()
#include <
string.
h> // required
for memset
() and strerror
()
#include <signal.h> // required for signal(), raise() and signal IDs
#include <sys/time.h> // required before including sys/select.h
#include <sys/socket.h> // required for socket() and connect()
#include <sys/select.h> // required for select()
#include <fcntl.h> // for fcntl()
#include <arpa/inet.h> // required for htons() and inet_addr()
#include <execinfo.h> // required for backtrace access
#include <unistd.h> // required for dup2()
#include <dispatch/dispatch.
h> // required
for GCD
#include <dlfcn.h> // for dlsym()
#include <errno.h> // for errno
#include <objc/runtime.h> // for objc_getClass()
// forward declaration of __cxa_demangle
() for C.
Normally in <cxxabi.
h>
which is only accessible when compiling C++ code.
// since we only need this
single function, don't bother including the whole header as it causes errors when the implicit include paths change.
extern char *__cxa_demangle
(const
char *mangled_name,
char *output_buffer, size_t *
length, int *status
);
//
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.
@interface UIDetachedAlertController : UIAlertController
@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
-
(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
@end
@implementation UIDetachedAlertController
- (void) viewDidLoad { [super viewDidLoad]; } // make sure the invisible window loads correctly
-
(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
-
(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
];
}
@end
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
// 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
#define alert(msg) dispatch_after (dispatch_time (DISPATCH_TIME_NOW, NSEC_PER_SEC), dispatch_get_main_queue (), ^{ \
if ([UIApplication sharedApplication
] !=
NULL) /*
is this a graphical app? note we can only test this after Cocoa has started */
{ \
if (objc_getClass
("UIAlertController"
) !=
NULL) /* does it support new style alert views? */
{ \
UIDetachedAlertController *alert_controller = [UIDetachedAlertController alertControllerWithTitle: @"Warning" message: @(msg) preferredStyle: UIAlertControllerStyleAlert]; \
[alert_controller addAction: [UIAlertAction actionWithTitle: @"OK" style: UIAlertActionStyleDefault handler: nil]]; \
[alert_controller show]; \
} else /* it wants old style alert views */ [[[UIAlertView alloc] initWithTitle: @"Warning" message: @(msg) delegate: nil cancelButtonTitle: @"OK" otherButtonTitles: nil] show]; \
})
// POSIX signal behaviours
#define SIGNAL_DISCARD 0
#define SIGNAL_TERMINATE 1
#define SIGNAL_CRASHDUMP 2
#define SIGNAL_PAUSE 3
// POSIX signals description structure
type definition
{
int is_hookable;
int posix_default;
sig_t prev_handler;
} signal_t;
// array of signal descriptions (MUST BE ALIGNED WITH signal.h VALUES!)
static signal_t signals[] =
{
{ 1, SIGNAL_TERMINATE,
NULL, "HUP", "terminal
line hangup"
},
{ 1, SIGNAL_TERMINATE,
NULL, "INT", "interrupt program"
},
{ 1, SIGNAL_CRASHDUMP,
NULL, "QUIT", "
quit program"
},
{ 1, SIGNAL_CRASHDUMP,
NULL, "ILL", "illegal instruction"
},
{ 1, SIGNAL_CRASHDUMP,
NULL, "TRAP", "
trace trap"
},
{ 1, SIGNAL_CRASHDUMP,
NULL, "ABRT", "abort
() call"
},
{ 1, SIGNAL_CRASHDUMP,
NULL, "EMT", "emulate instruction executed"
},
{ 1, SIGNAL_CRASHDUMP,
NULL, "FPE", "floating-point exception"
},
{ 0, SIGNAL_TERMINATE,
NULL, "KILL", "kill program"
}, // cannot be caught!
{ 1, SIGNAL_CRASHDUMP,
NULL, "BUS", "bus error"
},
{ 1, SIGNAL_CRASHDUMP,
NULL, "SEGV", "segmentation violation"
},
{ 1, SIGNAL_CRASHDUMP,
NULL, "SYS", "non-existent system call invoked"
},
{ 1, SIGNAL_TERMINATE,
NULL, "PIPE", "write on a pipe with no reader"
},
{ 1, SIGNAL_TERMINATE,
NULL, "ALRM", "real-time
timer expired"
},
{ 1, SIGNAL_TERMINATE,
NULL, "TERM", "software termination signal"
},
{ 1, SIGNAL_DISCARD,
NULL, "URG", "urgent condition present on socket"
},
{ 0, SIGNAL_PAUSE,
NULL, "STOP", "stop"
}, // cannot be caught!
{ 1, SIGNAL_PAUSE,
NULL, "TSTP", "stop signal generated from keyboard"
},
{ 1, SIGNAL_DISCARD,
NULL, "CONT", "
continue after stop"
},
{ 1, SIGNAL_DISCARD,
NULL, "CHLD", "child status has changed"
},
{ 1, SIGNAL_PAUSE,
NULL, "TTIN", "background read attempted from control terminal"
},
{ 1, SIGNAL_PAUSE,
NULL, "TTOU", "background write attempted to control terminal"
},
{ 1, SIGNAL_DISCARD,
NULL, "IO", "
I/O
is possible on a descriptor"
},
{ 1, SIGNAL_TERMINATE,
NULL, "XCPU", "CPU time limit exceeded"
},
{ 1, SIGNAL_TERMINATE,
NULL, "XFSZ", "file
size limit exceeded"
},
{ 1, SIGNAL_TERMINATE,
NULL, "VTALRM", "virtual time alarm"
},
{ 1, SIGNAL_TERMINATE,
NULL, "PROF", "profiling
timer alarm"
},
{ 1, SIGNAL_DISCARD,
NULL, "WINCH", "window
size change"
},
{ 1, SIGNAL_DISCARD,
NULL, "INFO", "status request from keyboard"
},
{ 1, SIGNAL_TERMINATE,
NULL, "USR1", "user-defined signal
1"
},
{ 1, SIGNAL_TERMINATE,
NULL, "USR2", "user-defined signal
2"
},
};
static signal_t *last_sig =
NULL;
static int is_repeating = 0;
static void __exit_handler (void)
{
// handy function to notify the user on the remote debug console that the
// process
is exiting by
return of main
() or by a call to exit
().
fprintf (stderr, "Your app exited by its own will.\n"
);
return;
}
static void __sig_handler (int signal_id)
{
// this very handy
function dumps the stack
trace to the standard
error stream
// in case the program crashes. Very useful with the remote debug console!
void *callstack[128];
char **backtrace_strings;
int backtrace_count;
int backtrace_index;
int prev_errno;
signal_t *sig;
prev_errno = errno; // have a copy of errno to keep it safe
//
is it one of the signals we know ?
if ((signal_id > 1) && (signal_id < (int) (sizeof (signals) / sizeof (signal_t))))
{
sig = &signals[signal_id]; // quick access to signal
if (sig != last_sig)
{
is_repeating = 0;
if (sig->posix_default == SIGNAL_CRASHDUMP)
{
fprintf (stderr, "SIG
%s received (%s) -- WHOA! I did something nasty. Dumping the stack trace:\n", sig->name, sig->desc);
backtrace_count = backtrace (callstack, sizeof (callstack) / sizeof (callstack[0]));
backtrace_strings = backtrace_symbols (callstack, backtrace_count);
for (backtrace_index = 0; backtrace_index < backtrace_count; backtrace_index++)
{
func = strstr
(backtrace_strings
[backtrace_index
], " _Z"
); //
is there a C++ mangled name in this
string ?
{
*func =
0; //
break the
string here
func++; // jump over it
offs = strstr (func, " + ");
{
*offs = 0;
offs++;
fprintf (stderr, "\t
%s %s %s\n", backtrace_strings[backtrace_index], __cxa_demangle (func, NULL, NULL, NULL), offs);
}
else
fprintf (stderr, "\t
%s\n", backtrace_strings[backtrace_index]);
}
else
fprintf (stderr, "\t
%s\n", backtrace_strings[backtrace_index]);
}
free (backtrace_strings);
}
else
fprintf (stderr, "SIG
%s received (%s)\n", sig->name, sig->desc);
}
else
{
if (is_repeating == 0)
fprintf (stderr, "SIG
%s repeating (%s) [future occurences will be ignored]\n", sig->name, sig->desc);
is_repeating = 1;
}
last_sig = sig; // remember last received signal to avoid excessive flooding
}
else
fprintf (stderr, "Signal
%d received (unknown signal)\n", signal_id);
// should this signal be ignored ?
if (sig->prev_handler == SIG_IGN)
{
errno = prev_errno; // restore errno
return; // and ignore signal
}
//
else is it a user-defined signal handler ?
if ((sig->prev_handler != SIG_ERR) && (sig->prev_handler != SIG_DFL))
{
errno = prev_errno; // restore errno
sig->prev_handler (signal_id); // let the default behaviour happen (don't call raise() here)
return; // and return
}
// else the expected behaviour must be SIG_DFL
signal (signal_id, SIG_DFL); // restore default signal handler
errno = prev_errno; // restore errno
kill (getpid (), signal_id); // call the default handler
return; // and return
}
int bootstrap_console (void)
{
// and here's the new program entrypoint.
// Compile with -Dmain(argc,argv)=__attribute__((visibility("default")))_rdc_main(argc,argv) -DREMOTE_CONSOLE="xxx.xxx.xxx.xxx"
// and link with -exported_symbol *_rdc_main* to make sure the old entrypoint
is exported by the linker and visible to dlsym
().
unsigned int sig_index;
int debug_socket[2];
// redirect the standard output and standard
error streams to a remote console
if ((debug_socket
[i] = socket
(AF_INET, SOCK_STREAM, IPPROTO_TCP
)) != -
1)
{
struct sockaddr_in debugserver_sockaddr;
fd_set socket_set;
memset (&debugserver_sockaddr, 0, sizeof (debugserver_sockaddr));
debugserver_sockaddr.sin_family = AF_INET;
debugserver_sockaddr.sin_addr.s_addr = inet_addr (REMOTE_CONSOLE);
debugserver_sockaddr.
sin_port = htons
(5001 +
i); // console listens on TCP ports
5000 (legacy
),
5001 (stdout
) and
5002 (stderr
)
fcntl
(debug_socket
[i], F_SETFL, O_NONBLOCK
); //
set the socket into non-blocking mode
connect
(debug_socket
[i],
(struct sockaddr *
) &debugserver_sockaddr, sizeof
(debugserver_sockaddr
)); // connect to remote console
FD_ZERO (&socket_set);
FD_SET
(debug_socket
[i], &socket_set
);
timeout.tv_sec = 3; // 3 seconds timeout
timeout.tv_usec = 0;
if (select
(debug_socket
[i] +
1,
NULL, &socket_set,
NULL, &timeout
) ==
1)
{
int socket_error;
socklen_t socketerror_size = sizeof (socket_error);
getsockopt
(debug_socket
[i], SOL_SOCKET, SO_ERROR, &socket_error, &socketerror_size
);
if (socket_error == 0)
{
fcntl
(debug_socket
[i], F_SETFL, fcntl
(debug_socket
[i], F_GETFL,
0) & ~O_NONBLOCK
); // put the socket back into blocking mode
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
}
else
alert ("Unable to connect to remote debug console at " REMOTE_CONSOLE " on ports 5001-5002.\n\nLogging will NOT be redirected.");
}
else
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."
);
}
else
alert ("Unable to create stdout/stderr socket to connect to remote debug console.\n\nLogging will NOT be redirected.");
// the next thing to do
is to trap signals with a custom handler to
help debugging
for (sig_index = 0; sig_index < sizeof (signals) / sizeof (signal_t); sig_index++)
if (signals[sig_index].is_hookable)
{
signals[sig_index].prev_handler = signal (sig_index, __sig_handler); // hook every possible signal
if (signals[sig_index].prev_handler == SIG_ERR)
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);
}
//
now register a normal exit handler
function to
catch normal exits
if (atexit (__exit_handler) != 0)
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));
//
I didn't know that it was impossible, so
I did it.
fprintf (stderr, "Hello!
I'm your app and this
is my first message to stderr =
)\n"
);
return (0);
}
#endif // REMOTE_CONSOLE