This XOR That
Something. Or something else. (But not both.)
Monday, June 21, 2021
Software estimation formula
Monday, February 17, 2020
Do Your Dharma
The Loneliest Animal
Sunday, October 4, 2015
Into the Looking Glass
So, the quick fix was a mirror: the character can't describe herself from outside, but he can certainly observe his appearance when looking into a mirror. "Does this chartreuse fez make the mole on the left side of my nose look big?" pondered Brad as he gazed narcissistically into the mirror at his tall, blond, Midwesterny beauty. Nice trick, but... It's so pedestrian, and usually meaningless.
So then we come to the first chapter of Ulysses, in which stately, plump Buck Mulligan produces the cracked looking-glass of a servant from a pocket and hands it to Stephen Dedalus, our point-of-view character for that chapter. And it's the mirror trick, here comes the description of Stephen's face, his teeth, his eyes... But it never comes.
Joyce gives us something else entirely. Stephen isn't going to use the mirror as we expect. This isn't an undergrad short story. First, commanded to look at himself in the mirror, Stephen sees his hair on end... And reflects on the fact that this is the face that others see, with no mention of its appearance. Stephen then observes the mirror, the cracked looking-glass of a servant, as a symbol of Irish art. And Mulligan puts in his oar, giving us a tale of the mirror's provenance. And then, wait for it, Joyce winks at us, denying us a view of Stephen, just as he denies Stephen a view of himself, "the rage of Caliban at not seeing his face in a mirror".
Monday, October 29, 2012
Navigation
Navigation
The ocean is a boiling vat of steel,
And overhead the sky pours forth its wrath.
No radiation storm in charted space
Hath fury like the homeworld's primal seas.
My vessel sped from the icy rings of Saturn and my home among them, leaving Titan station and hurtling sunward. Past the treacherous eddies of Jupiter and the shoals of the asteroid belt I piloted her toward a distant home I had never known.
The waves are crashing on the forward desk
While wind-whipped rain assaults my naked face.
The pitching boat beneath me groans in pain
From stresses never dreamt of deep in space.
Finally I came to the safety of Earth Harbor, high above the Amazon jungle. Leaving my ship in port, a ferry carried me to land at another kind of harbor.
The raging of the storm has now been spent
As pelting rain and driving seas relax.
The clouds disperse, and silence rules the night
Beneath the visage of the starry sky.
Sunday, January 10, 2010
Choose indecision: Simple plugins in C
In my last post, I talked a bit about using shared libraries to change the implementation of some existing program at runtime. That was a nice trick for testing or debugging, but it's probably not how you would want to approach the task of making a system that's extensible and configurable.
Which brings us to the topic of today's entry: using dynamically loaded libraries as plugins to make C programs extensible and configurable. A plugin is just a shared library that provides an implementation of a particular interface, and which can be swapped for another implementation by some kind of runtime parameter.
A (contrived) hard-coded example
Suppose you have a piece of code that is prone to segmentation faults, and you want to get notification when such a fault occurs. (OK, this is sort of corny, because you would really want a core file, rather than just a log message, but it makes a fun example.)
You might begin by hard-coding an signal handler into your application, which will log the event. Here's an example of a program that just sets up a simple signal handling callback that writes a message to a logfile before it exits:
hardcoded.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <signal.h> #include <errno.h> #include <sys/types.h> #include <dlfcn.h> /* This is our signal-handling callback. */ static void segv_callback(int sig) { FILE *log = fopen("logfile", "a"); fprintf(log , "Your program segfaulted. Sorry.\n"); fclose(log); exit(1); } /* This is our main service method, which does all the work. * (Or it would, if this example were about doing work.) */ static void do_stuff(void) { fprintf(stderr, "Doing stuff...\n"); sleep(5); } /* Set the handling of SIGSEGV to call our callback. */ static void init_signal_handler(void) { struct sigaction sigact; memset(&sigact, 0, sizeof sigact); sigact.sa_handler = segv_callback; if (sigaction(SIGSEGV, &sigact, NULL) != 0) { fprintf(stderr, "Can't set up SEGV handler: %s\n", strerror(errno)); exit(1); } } int main(int ac, char **av) { init_signal_handler(); /* Here is where we fake a segmentation fault. */ kill(getpid(), SIGSEGV); do_stuff(); return 0; }
The interesting bit in this program is in init_signal_handler
, where we use sigaction to specify the action that the process should take when it receives the signal SIGSEGV, which is what happens when the OS detects a segmentation violation.
Making it configurable
Now, suppose you don't want the response to the event hard-coded. Maybe some users want logging while others want some kind of email notification. You could hard-code this, too, with conditionals and a set of command-line arguments for each, but you could also use plugins to make the available options completely customizable. So, we're going to implement a plugin-based solution now.
First, we will define an interface for the plugins. In this simple example, the interface is just that the plugin must have a function called "segv_callback", which takes one integer argument:
void segv_callback(int);
Now, this interface isn't enforced by the compiler, so your program
will need to handle failures when you attempt to look up the callback function by name. (This is something you never need to worry about in either statically-linked programs, where the compiler gives an error if you try to call an unknown function, or in normal dynamically-linked programs, where the linker will give an error if all the symbols aren't resolved.)
Once we know the interface for the plugin, we can write a function to load the plugin library and initialize a function pointer to the segv_callback
function in the library. Here is a function that does this.
/* This is just a function pointer. */ static void (*segv_callback)(int); /* This method sets the function pointer segv_callback to point * to the method in the shared library file named by callback_lib. */ static void init_callback(char *callback_lib) { void *callback_handle; if ((callback_handle = dlopen(callback_lib, RTLD_LAZY | RTLD_LOCAL)) == NULL) { /* We failed to open the callback library. Fail. */ fprintf(stderr, "Failed to open callback library: %s\n", dlerror()); exit(1); } if ((segv_callback = dlsym(callback_handle, "segv_callback")) == NULL) { fprintf(stderr, "Failed to get callback function: %s\n", dlerror()); exit(1); } }
You call this function with the name of the library to be used as the plugin, and the function uses dlopen to get a handle to the library. Then it uses dlsym to get a pointer to the function named "segv_handler" withing that library. Finally, it sets the value of the (global) function pointer segv_callback
to be the pointer that dlsym returned.
Here is one possible implementation of segv_callback
, which sends email to the user running the process (to be precise, to the username corresponding to the effective UID for the process).
mail_plugin.c
#include <stdio.h> #include <pwd.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> void segv_callback(int signal) { FILE *mail; struct passwd *pwd; char *username = "root"; char buf[256]; memset(buf, 0, sizeof(buf)); if ((pwd = getpwuid(geteuid())) != NULL) { username = pwd->pw_name; } snprintf(buf, 255, "/usr/bin/mail %s", username); if ((mail = popen(buf, "w")) != NULL) { fprintf(mail, "I'm sorry. Your program segfaulted. Have a nice day.\n"); pclose(mail); } exit(1); }
Here is another implementation of this plugin interface that just does logging, exactly like the original program:
logging_plugin.c
#include <stdio.h> #include <stdlib.h> void segv_callback(int signal) { FILE *log = fopen("logfile", "a"); fprintf(log , "Your program segfaulted. Sorry.\n"); fclose(log); exit(1); }
When you've built the program and the plugin, you can run it like this:
$ ./simple_pluggable ./mail_plugin.so $ mail Mail version 8.1.2 01/15/2001. Type ? for help. "/var/mail/chris": 1 message 1 new >N 1 chris@localhost Sun Jan 10 16:01 13/433 & Message 1: From chris@localhost Sun Jan 10 16:01:11 2010 X-Original-To: chris To: chris@localhost Date: Sun, 10 Jan 2010 16:01:11 -0800 (PST) From: chris@localhost (Chris) I'm sorry. Your program segfaulted. Have a nice day.
A little bit of state
Now, the plugin code above just sends email to the logged-in user. But what if we want to specify a particular email account where such notifications should be sent at runtime? Then the plugin needs to get this information from somewhere.
It's possible that you could have a configuration file for this, or a lookup service of some kind (LDAP perhaps) that the plugin could call to get contact details. However, let's say that the desired behavior is to specify the email address on the command line, while you are selecting the email callback. In this case, the plugin needs to get that information somewhere, and in this example, it will come from a plugin initialization call.
In order to support initialization, the (informal) interface for your plugin needs another function, which we'll call init
:
void init(char *arg);
void segv_callback(int);
The idea here is that you will initialize the plugin with some data, and the plugin will maintain the data in memory until it is needed when segv_callback
is called. In this case, we're just storing a single string, but the initialization could be anything at all.
Here is a new version of the init_callback
function (the function that loads a plugin) that we defined earlier. The difference is that this version takes a parameter, which is passed to the plugin's init
function, if there is one.
/* This method sets the function pointer segv_callback to point * to the method in the shared library file named by callback_lib. * * It also calls the init method in that library. */ static void init_callback(char *callback_lib, char *init_arg) { void *callback_handle; void (*init)(char*); if ((callback_handle = dlopen(callback_lib, RTLD_LAZY|RTLD_LOCAL)) == NULL) { /* We failed to open the callback library. Fail. */ fprintf(stderr, "Failed to open library: %s\n", dlerror()); exit(1); } /* init_arg, if it exists, is an argument to initialize plugin. */ if ((init = dlsym(callback_handle, "init")) != NULL) { if (init_arg != NULL) { /* We only call init if it's defined * and we have an arg for it. */ init(init_arg); } else { /* in this case, we need an init arg, but none specified. */ fprintf(stderr, "Need an init arg for this plugin.\n"); exit(1); } } if ((segv_callback = dlsym(callback_handle, "segv_callback")) == NULL) { fprintf(stderr, "Failed to get function: %s\n", dlerror()); exit(1); } }
And here is a plugin that sends mail to user whose address is passed to the init
function:
config_mail_plugin.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> static char *email_address = NULL; /* Store a copy of the passed-in address. */ void init(char *addr) { email_address = strdup(addr); } void segv_callback(int signal) { FILE *mail; char buf[256]; if (email_address == NULL) { fprintf(stderr, "Email address has not been initialized.\n"); exit(1); } memset(buf, 0, sizeof(buf)); snprintf(buf, 255, "/usr/bin/mail %s", email_address); if ((mail = popen(buf, "w")) != NULL) { fprintf(mail, "I'm sorry %s. Your program has segfaulted.\n", email_address); pclose(mail); } exit(1); }
When you've built this code and the plugin, you can run it like this:
$ ./stateful_pluggable ./config_mail_plugin.so chris@example.com
And then check your mailbox for notifications.
The End
This has been a demonstration on a fairly simple way to construct plugins on any kind of UNIX-ish system that supports dlsym/dlopen. If you are targeting any other system, you'll have to find out how to load libraries somewhere else.
Appendix: Files that aren't already listed above
Makefile
CFLAGS = -fPIC all: hardcoded simple_pluggable stateful_pluggable logging_plugin.so mail_plugin.so config_mail_plugin.so hardcoded: hardcoded.o $(CC) -o hardcoded hardcoded.o simple_pluggable: simple_pluggable.o $(CC) -o simple_pluggable simple_pluggable.o -ldl stateful_pluggable: stateful_pluggable.o $(CC) -o stateful_pluggable stateful_pluggable.o -ldl logging_plugin.so: logging_plugin.o $(CC) -o logging_plugin.so logging_plugin.o -shared mail_plugin.so: mail_plugin.o $(CC) -o mail_plugin.so mail_plugin.o -shared config_mail_plugin.so: config_mail_plugin.o $(CC) -o config_mail_plugin.so config_mail_plugin.o -shared clean: rm -f *.so *.o logfile
stateful_pluggable.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <signal.h> #include <errno.h> #include <sys/types.h> #include <dlfcn.h> /* This is just a function pointer. */ static void (*segv_callback)(int); /* This is our main service method, which does all the work. * (Or it would, if this example were about doing work.) */ static void do_stuff(void) { fprintf(stderr, "Doing stuff...\n"); sleep(5); } /* This method sets the function pointer segv_callback to point * to the method in the shared library file named by callback_lib. * * It also calls the init method in that library. */ static void init_callback(char *callback_lib, char *init_arg) { void *callback_handle; void (*init)(char*); if ((callback_handle = dlopen(callback_lib, RTLD_LAZY|RTLD_LOCAL)) == NULL) { /* We failed to open the callback library. Fail. */ fprintf(stderr, "Failed to open library: %s\n", dlerror()); exit(1); } /* init_arg, if it exists, is an argument to initialize plugin. */ if ((init = dlsym(callback_handle, "init")) != NULL) { if (init_arg != NULL) { /* We only call init if it's defined * and we have an arg for it. */ init(init_arg); } else { /* in this case, we need an init arg, but none specified. */ fprintf(stderr, "Need an init arg for this plugin.\n"); exit(1); } } if ((segv_callback = dlsym(callback_handle, "segv_callback")) == NULL) { fprintf(stderr, "Failed to get function: %s\n", dlerror()); exit(1); } } /* Set the handling of SIGSEGV to call our callback. */ static void init_signal_handler(void) { struct sigaction sigact; memset(&sigact, 0, sizeof sigact); sigact.sa_handler = segv_callback; if (sigaction(SIGSEGV, &sigact, NULL) != 0) { fprintf(stderr, "Can't set up SEGV handler: %s\n", strerror(errno)); exit(1); } } int main(int ac, char **av) { char *init_arg = NULL; /* We're going to use the first command line argument, which will * specify which callback library to load. */ if (ac < 2) { fprintf(stderr, "Usage: stateful_pluggable PLUGIN_FILE [arg]\n"); exit(2); } if (ac > 2) { init_arg = av[2]; } init_callback(av[1], init_arg); init_signal_handler(); kill(getpid(), SIGSEGV); do_stuff(); return 0; }
simple_pluggable.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <signal.h> #include <errno.h> #include <sys/types.h> #include <dlfcn.h> /* This is just a function pointer. */ static void (*segv_callback)(int); /* This is our main service method, which does all the work. * (Or it would, if this example were about doing work.) */ static void do_stuff(void) { fprintf(stderr, "Doing stuff...\n"); sleep(5); } /* This method sets the function pointer segv_callback to point * to the method in the shared library file named by callback_lib. */ static void init_callback(char *callback_lib) { void *callback_handle; if ((callback_handle = dlopen(callback_lib, RTLD_LAZY | RTLD_LOCAL)) == NULL) { /* We failed to open the callback library. Fail. */ fprintf(stderr, "Failed to open callback library: %s\n", dlerror()); exit(1); } if ((segv_callback = dlsym(callback_handle, "segv_callback")) == NULL) { fprintf(stderr, "Failed to get callback function: %s\n", dlerror()); exit(1); } } /* Set the handling of SIGSEGV to call our callback. */ static void init_signal_handler(void) { struct sigaction sigact; memset(&sigact, 0, sizeof sigact); sigact.sa_handler = segv_callback; if (sigaction(SIGSEGV, &sigact, NULL) != 0) { fprintf(stderr, "Can't set up SEGV handler: %s\n", strerror(errno)); exit(1); } } int main(int ac, char **av) { char *init_arg = NULL; /* We're going to use the first command line argument, which will * specify which callback library to load. */ if (ac < 2) { fprintf(stderr, "Usage: simple_pluggable PLUGIN_FILE\n"); exit(2); } init_callback(av[1]); init_signal_handler(); kill(getpid(), SIGSEGV); do_stuff(); return 0; }
Wednesday, December 16, 2009
Give up the func(tion)
However, strace only shows you the system calls. So, while you see every file that is opened or closed, every byte that is written to a file descriptor, and every signal that is received, you don't see any indication of how any user functions are called or their results.
Typically, if you want to see this level of detail, you run your program in a debugger, where you can see its moment-by-moment execution. But sometimes what you want is just to insert some logging into an existing program. This is no problem if you have the source, but what if you don't?
As it happens, you can do this pretty easily, using the LD_PRELOAD environment variable and the dynamic linking loader. I'll run through a contrived example here.
Suppose I want to log every time my program calls the function
gets()
, because it's a really bad function for anyone to use.I would write a wrapper function like this:
char *gets(char *s) {
static char *(*original_gets)(char*) = NULL;
char *result;
if (original_gets == NULL) {
original_gets = dlsym(RTLD_NEXT, "gets");
}
fprintf(stderr, "Someone called gets!!!\n");
result = original_gets(s);
fprintf(stderr, "And here is what they got with gets: %s\n", result);
return result;
}
There are just two important things here. First, the function has the same name and arguments as the original function, and second, that the function can use
dlsym()
with the RTLD_NEXT option to get a reference to the "next" implementation of the gets()
function.dlsym()
is one of the functions that POSIX defines for interfacing with the dyamic linking loader. dlsym()
is the one that allows you to resolve a particular name in various ways. The one I'm using, which is activated by the RTLD_NEXT parameter, indicates that the next definition for the given name should be returned. This allows application writers to do exactly what we're doing here: writing wrappers.So, now that I've defined a wrapper for the normal
gets()
function, I put it into a shared library.Finally, to use my wrapper, I set it as the value of the LD_PRELOAD environment variable when running a program, like this:
$ env LD_PRELOAD=./libfunc.so ./getter
This runs a program named "getter" in the current directory.
Here is the sample output:
Someone called gets!!!
Hello
And here is what they got with gets: Hello
Here is your input: Hello
So, now you know how to write a wrapper in C. Enjoy.
Here are the full sources for the code discussed in this post:
func.c:
/* Need to define _GNU_SOURCE for RTLD_NEXT to be available on Linux.
* (Since it's not part of Posix.)
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
char *gets(char *s) {
static char *(*original_gets)(char*) = NULL;
char *result;
if (original_gets == NULL) {
original_gets = dlsym(RTLD_NEXT, "gets");
}
fprintf(stderr, "Someone called gets!!!\n");
result = original_gets(s);
fprintf(stderr, "And here is what they got with gets: %s\n", result);
return result;
}
getter.c:
#include <stdio.h>
int main(int ac, char **av) {
char buf[64]; /* should be enough, right? */
gets(buf);
printf("Here is your input: %s\n", buf);
return 0;
}
Makefile:
CFLAGS = -fPIC
LDFLAGS = -shared -ldl
all: libfunc.so getter
libfunc.so: func.o
$(CC) -o libfunc.so func.o $(LDFLAGS)
func.o: func.c
getter: getter.o
cc -o getter getter.o
getter.o: getter.c
test: getter libfunc.so
env LD_PRELOAD=./libfunc.so getter
clean:
rm -f *.so *.o getter
Software estimation formula
This software eatimation formula is inspired by Fred Brooks' observations about the effect of team size on project schedule in The Mythi...
-
Once upon a time, there lived a band of ordinary people. They moved from place to place, gathering and hunting to feed themselves. They li...
-
The Bhagavad Gita is one of the classic Hindu sacred texts, describing a conversation between a prince named Arjuna (who is faced with c...
-
In my last post , I talked a bit about using shared libraries to change the implementation of some existing program at runtime. That was a n...