Monday, June 21, 2021

Software estimation formula

This software eatimation formula is inspired by Fred Brooks' observations about the effect of team size on project schedule in The Mythical Man-Month.

T = (S*W/D + (1-S)*W*D) / D

Where:
- T is the calendar time required
- W is the total work required
- D is the number of devs
- S is the separabilty of the task, ranging from 0 (not at all separable) to 1 (perfectly separable)


Monday, February 17, 2020

Do Your Dharma


The Bhagavad Gita is one of the classic Hindu sacred texts, describing a conversation between a prince named Arjuna (who is faced with civil war against many of his closest friends and relatives) and his chariot driver Krishna (who, unbeknownst to Arjuna, happens to be an incarnation of the god Vishnu). Arjuna is despairing at the prospect of either defeat and loss of his own life and those of his allies, or victory that is empty due to the deaths of his loved ones on the other side. Arjuna has thrown his bow onto the ground and sat down on the back of his chariot when Krishna comes to offer him counsel on his dilemma. In the course of this conversation, Krishna reveals his true identity and gives Arjuna guidance not only on his current situation, but also on the way to live for his spiritual health and for the good of the world.

One of the central questions of the Bhagavad Gita may be stated as: since we can never be sure of the consequences of our actions, how can we make choices without being paralyzed by fear over the results of our decisions? This is of central importance to Arjuna as he struggles with his decision over whether to  fight or surrender. The answer, according to the Gita, is to diligently do those things that are your “dharma”  (which may be interpreted as duty or responsibility) simply because they are your dharma, and without expectation or concern for whether the result will be successful or not. Letting concern over the success or failure of the action leads to confusion and error in the world of chance and illusion. Doing your dharma because it is your dharma is the only way to be free from this entanglement.

And what is your dharma? The particulars of your dharma change from person to person, and at different times of your life. But the common thread is selfless service.

What’s so special about selfless service?

In order to understand that, you have to understand samsara: the idea that a person has been born countless time before and will be born countless times again, and karma: the idea that the consequences of your actions will influence how you are reborn into a new life. In the Gita, this is a literal rebirth of your soul into a new body, though without any memory of your past lives. This rebirth may allow you to move closer to (if your actions in life were selfless) or further from (it your actions were selfish) the ultimate goal  of reaching a state of oneness with Krishna, with the universal soul, and with the universe.

What’s interesting is that karma is demonstrably true, even without belief in reincarnation. Every choice you make is only possible due to the choices made by other people in the past, and every future life is impacted by your choices. The future lives affected by your actions aren't necessarily your soul in a new body, but rather every person who lives in the world that has been created by your actions. And, though every person is unique, we are all deeply connected, so that you can read ancient texts and see ancient art and observe ancient homes, and see that the passage of time has not made people noticeably different in their hopes, fears, obsessions, and needs. And you can look around you, in other countries and in your own neighborhood, to see that language, skin color, religion, and nation do not make people really different in these ways, either.

But the insight of karma goes deeper than this. A forest exists only through the life of the trees in it. But the trees would not be there, and certainly not growing in the same way, without the rest of the forest around them. Every seed that sprouts, every tree that grows, brings new life to the forest. Likewise, humanity exists only through the lives of individual humans, human joy exists only through the joy of individual humans, human suffering exists only through the suffering of individual humans. And individual humans can only survive, can only achieve happiness, and can only alleviate suffering as part of the whole of humanity.  Karma means that your decisions in this life affect all people, from now until forever. Some future “you” (who may or may not be you reincarnated) is going to have a better or worse life based on the decisions you make today, because your actions affect the forest of humanity. 

And one of the people affected by your choices today is the future you, in your present incarnation, days, months, or years down the line. This is similar to the colloquial interpretation of “karma”, essentially that “what goes around, comes around”, that your actions will be rewarded or punished in kind in this life. 

So what is my dharma?

That's a question for each person on Earth. Each of us has different insights into the suffering of our neighbors, different skills we may use to assist them, and different balances of responsibilities to others that we have taken on, but none of us has the power and skills to help every suffering individual. No person knows the full extent of all human suffering, and, at any given moment, any one of us may be suffering. 

Ask yourself what kind of world you want to want to be reborn in (or if you’re not inclined to believe in reincarnation, what kind of world you want for someone whose hopes and dreams and feelings are like yours). Do you want this future you to be able to escape the pain that you’ve experienced, to have the wisdom to avoid making the mistakes you've made? Or do you want to pile additional suffering onto the future you? Reincarnation, whether of self or of not, knows no distinctions of race, gender, occupation, nation, or political party. All you can know about the future you is that they experience all of life’s joy and pain just like you, and that they are trying to find a way to be happy, or at least escape misery. What can you, a person who is just as much in pursuit of happiness and pursued by misery, do to help all these future yous?

The answer to this question is your dharma.

At the end of the Bhagavad Gita, Prince Arjuna is persuaded by Krishna’s teaching, and he resolves to take action, to do his dharma. Though the battle seems hopeless and its end unknowable, Arjuna will fight.

The Loneliest Animal


Once upon a time, there lived a band of ordinary people. They moved from place to place, gathering and hunting to feed themselves. They lived and loved and raised their children, teaching them as well as they could about how to survive in a difficult world. And their children laughed and played and learned from watching the adults. They felt love, fear, hatred, joy, sorrow, and pain, just like us. They experienced the world using senses identical to ours. Their brains were just as capable of learning and reasoning about the world as ours. They imagined and dreamed. But they had no words to express any of this, because they lived before language was invented.

               Who were these smart-but-silent people? Fossil evidence points to the existence of modern humans 200,000 years ago, but the oldest example of “behavioral modernity” appear to date to around 70,000 years ago. Behavioral modernity means that the people who lived then were taking part in activities that we think of as uniquely human. Some examples of such activities that leave a clear trace in the debris and artifacts of the time are complex toolmaking, burial of the dead, and art representing human and animal figures. What’s strange about this is that there is a gap of 130,000 years, during which humans who were biologically no different from us lived lives that were largely indistinguishable from the lives of our earlier ancestors.

It’s inconceivable that people would simply exist without trying anything new for 130,000 years, unless we’re missing some key difference between those early modern humans and ourselves. If there’s no biological difference, then there must be some technological innovation that suddenly unlocked our creativity. But what technology could be transformative enough to make such a huge difference in humans’ ability to create tools, religion, and culture?

I propose that none of these modern behaviors were possible without language, which would mean that for 130,000 years, modern humans went through their entire lives unable to communicate any of their thoughts to each other, but with the same desire to be understood as we have.[1] A human living in this silent era would have had the most complex brain ever yet seen on Earth, with the complexity of feeling and emotion that we see in our own friends, but with no way of expressing these feelings to any other being. Such a human would have been the loneliest animal ever to have lived.

               In order to understand what it means to live without language, consider how language functions in our daily lives. Without language, we wouldn’t be reading or writing. We wouldn’t be speaking to each other. Without language, there is no way for people to share their feelings and impressions and thoughts, no way to connect to another person on the intellectual level. Without language, no activity that involves using words as an abstraction for the universe is possible.  This means that formal logic is impossible. Arithmetic is impossible. Philosophy is impossible.

Without language, even thinking is impossible.

Isn’t it? 

Our inner voice, the thing we use when we “think”, would be impossible without a language to form its sentences. But not every task that requires our human intelligence makes use of the inner voice. For example, when I’m driving, my mind is building theories of the position and velocity of other vehicles, evaluating the opportunities and benefits of switching to a different lane, noticing debris in the road, and even appreciating the beauty of the morning sky, all without involving the inner voice to narrate these facts. In fact, it’s quite easy to think of entirely different subjects during the course of a long drive.

Similarly, any physical action clearly involves the planning and direction of the mind, but the inner voice is not involved. This is good news for our language-less ancestors, since waiting for language to be invented would not be a successful evolutionary strategy for a species that needs to eat, mate, and escape predators.

Any sensory experience is nonverbal first, and only occasionally enters your inner voice’s narration. So for example, when your alarm clock goes off in the morning, your mind doesn’t have to formulate the words “alarm clock”, in order for your body to react to the alarm, hit the snooze button, and go back to sleep. Or when you drink a cup of coffee while reading, you can enjoy and appreciate the taste and aroma of the coffee without your inner voice being sidetracked from narrating the words that you see on the page, and you can enjoy coffee without ever thinking the words “taste” or “aroma”.

Nonverbal experiences and thoughts are the norm for our lives. Only rarely do we focus on anything intensely enough for that thing to enter into our narrated stream-of-consciousness. Mostly, our inner voice is so busy with other tasks (such as playing commercial jingles that we last heard in childhood, or replaying old conversations to formulate what we should have said) that waiting for it to catch up with our activities would force us to live life at a crawl.

The disconnect between our experiential consciousness and our inner voice has a number of components, but the one is especially interesting is the fact that we are only able to formulate a tiny fraction of our experience into words. Quick: describe the taste of a persimmon to someone who has never had one before, or the smell of a rose, or the feeling of falling in love. But the words are only the narration and organization of the ideas. The ideas themselves are beneath, behind them, not in words, but something that can’t be put into words.

Another way of looking at the question of the function of the inner voice is suggested by the idea of “qualia”. The concept of qualia is that there are certain aspects of human experience that can never be communicated in words (they are “ineffable”), but which exist in consciousness. In philosophy of mind, the existence of qualia is controversial, with some philosophers arguing against the very existence of qualia, while others use qualia as support for a variety of philosophic stances.

However, if we begin with the idea that language is a human construct, and that consciousness arose before language was invented, and that consciousness is a prerequisite to being able to invent language in the first place, then the existence of qualia is completely understandable and expected. Before there was language, all experience was ineffable (since we had no words to communicate with), and therefore “qualia” would simply refer to “experience”. As we have developed language, we have learned how to communicate some fairly complex experiences, but our ability to describe our impressions with words can never begin to approach the immersive multisensory experience of the world as we perceive it.

Language is the first invention of humans to allow us to communicate our thoughts to each other, to touch the minds of others, and our languages have filled this role of communication medium fairly well. The ability to use language is a skill. It is not a skill that we are born with. Rather, it is a skill that we learn from our parents, our siblings, and the people we live with.

So how was language invented?

First, it's possible that some early human thinker, in a burst of genius that leaves Newton in the dust, came up with the idea on her own. Calling this “genius” is a reflection of the fact that language is a development that people were equipped to make for tens to hundreds of thousands of years, but it took all that time for someone to actually achieve. Humans had already had the intelligence needed to learn language—what was missing was the breakthrough of inventing a language to learn. Once the breakthrough was made, the idea of language spread to all humans who encountered it. It's akin to Newton's invention of calculus as a new mathematical language, where the invention was a work of genius, but any high school student can learn what Newton invented.

But the invention of language is a strange and unique occurrence, because it's not enough to simply to design and implement the idea. Inventing language is only really "done" if you can also teach it to someone else, and use the language to communicate with them, and they teach it to someone else, and so on. And keep in mind that “language” at this point was only spoken language, which means that only an invention of language that also included passing on the invention by directly and personally teaching others could possibly lead to the spread of language. This requirement, in my view, seems to point to a single inventor of language, maybe a mother who taught her invention to her young children, since children are the fastest learners of many new skills, particularly languages.

Language could also have evolved within a single social group over a long period of time, starting just as a collection of signals or words with no connections between them, slowly adding complexity and subtlety until it turns into something that can refer to itself, something that can loop back to become not just about particular concrete objects, but about the speaker's feelings and sensations and desires and needs and experiences and knowledge, and even about the words of others. Such an evolutionary view of language provides another possible pathway between the silent people discussed at the start of this essay and the rich history of art and language that sprang up after them.

               What’s the point of all this?

               First, consciousness does not require language. Conscious, social, learning, perceiving humans lived long before language was invented, and most of the events and perceptions of our daily lives are experienced without our crafting words to describe them. This means that research into consciousness that uses language as its starting point is looking in the wrong place, like trying to understand petroleum chemistry by examining automobiles.

               Second, qualia are not evidence for a separate plane of existence. Rather, they are evidence that language alone is not sufficient to communicate the fullness of human experience. Language is the first way that we’ve invented for communicating ideas between minds. We may be able to invent better ways to communicate, ways in which it is easy to express things that are inexpressible today, but currently our most effective way to touch another person’s mind is by putting together words into speech or writing.

               Finally, by understanding that language is a medium of communication between conscious minds, we can approach the problem of understanding consciousness without the confusion of attempting to also explain the tools invented by consciousness. Language is one such tool, as are logic, geometry, and mathematics. These tools are useful in the mind’s attempts to understand and be understood by other minds. However, these tools are not the mind itself, so representing the mind as a collection of logical states is also looking at the wrong level.

               Unlike those lonely humans who lived before language, modern humans are sometimes able to overcome the isolation described earlier. We can bring our minds into contact with each other through the mechanism of language, connecting with each other to express our thoughts, to teach our experiences, and to share our knowledge of the world.


12 February 2017

(Originally published on docs.com by me.)



[1] Not to mention our Homo erectus ancestors, who lived for almost two million years without language, and with almost the same brains and senses as ours.

Sunday, October 4, 2015

Into the Looking Glass

I was in an undergraduate creative writing class a number of years ago, and I remember how many of critiques of our writing involved the incongruity of a detailed description of a character appearing in a narrative from that character's point of view. "Maybe I want the cheesecake," thought Brad, a tall blond, brown-eyed Midwesterner with a mole on the left side of his nose wearing a chartreuse fez.

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

(A poem I wrote in 1993. It was rejected for publication, and sat in a box for the next 19 years.)

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)

strace is one of my favorite tools for debugging in Linux. If you've never used strace, it's a tool that prints (on stderr) the system calls executed by a program, with its arguments and the return value of the system call. Looking at the output of strace, you can step through a lot of a program's most interesting interactions, and see where something unexpected happens.

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...