John Tsiombikas nuclear@mutantstargoat.com
3 November 2014
Android applications are strange beasts. Even though under the hood it's basically a UNIX system using Linux as its kernel, there are other layers between (native) android apps and the bare system, that some things are bound to fall through the cracks.
When developing android apps, one generally doesn't login to the actual device
to compile and execute the program; instead a cross-compiler is provided by
google, along with a series of tools to package, install, and execute the app.
The degrees of separation from the actual controlling terminal of the
application, make it harder to just print debugging messages to the stdout and
stderr streams and watch the output like one could do while hacking a regular
program. For this reason, the android NDK provides a set of logging functions,
which append the messages to a global log buffer. The log buffer can be viewed
and followed remotely, from the development machine, over USB, by using the
adb logcat
tool.
This is all well and good, but if you're porting a program which prints a lot
of messages to stdout
/stderr
, or if you just like the convenience of using the
standard printf
/cout
/etc functions, instead of funny looking stuff like
__android_log_print()
, you might wonder if there is a way to just use the
standard I/O streams instead, right? Well so did I, and guess what... this is
still UNIX so the answer is of course you can!
The simplest solution to this problem would be if we could just redirect stdout
and stderr
to wherever these __android_log_whatever
functions are writing
their messages. This will obviously be either a socket or a pipe of some sort,
but alas it's not exposed in any way through the NDK APIs. We could figure out
where it goes if we where to dig through JNI calls and Java classes in the
intermediate layers, but a change in a future version of android or its SDKs,
might break our code if we use such undocumented ways to log messages. I'd
rather not go through all this trouble, and introduce incompatibilities in the
process.
An easier, albeit slightly more convoluted way to attack this, which I ended up
using, is to create a pipe, redirect stdout
and stderr
to the write end of the
pipe, and start a logging thread which just blocks reading the other end of
that pipe, and calls the NDK log functions to log whatever is coming through.
Here's a short snippet from my android logger demonstrating how to set that up:
static int pfd[2];
static pthread_t thr;
static const char *tag = "myapp";
int start_logger(const char *app_name)
{
tag = app_name;
/* make stdout line-buffered and stderr unbuffered */
setvbuf(stdout, 0, _IOLBF, 0);
setvbuf(stderr, 0, _IONBF, 0);
/* create the pipe and redirect stdout and stderr */
pipe(pfd);
dup2(pfd[1], 1);
dup2(pfd[1], 2);
/* spawn the logging thread */
if(pthread_create(&thr, 0, thread_func, 0) == -1)
return -1;
pthread_detach(thr);
return 0;
}
static void *thread_func(void*)
{
ssize_t rdsz;
char buf[128];
while((rdsz = read(pfd[0], buf, sizeof buf - 1)) > 0) {
if(buf[rdsz - 1] == '\n') --rdsz;
buf[rdsz] = 0; /* add null-terminator */
__android_log_write(ANDROID_LOG_DEBUG, tag, buf);
}
return 0;
}
For those not familiar with UNIX system programming, redirection of file
descriptor is done by closing the fd
in question and duplicating another
existing file descriptor in its place (with dup
or dup2
). Also you'll notice in
the logging thread I'm removing the trailing newline if it's there. That is
because __android_log_write
will append its own newline to any messages we log
through it.
Anyway, by calling start_logger
somewhere at the start of android_main
,
anything printed from that point onwards to stdout
/stderr
will end up in
the android log. Feel free to use it in your programs if you find it useful.
This was initially posted in my old wordpress blog. Visit the original version to see any comments.