The Kernel Kit: Images

Declared in: be/kernel/image.h

Library: libroot.so


Overview

An image is compiled code. There are three types of images:

An app image is an application. Every application has a single app image.

A library image is a dynamically linked library (a "shared library"). Most applications link against the system libraries (libroot.so, libbe.so, and so on) that Be provides.

An add-on image is an image that you load into your application as it's running. Symbols from the add-on image are linked and references are resolved when the image is loaded. An add-on image provides a sort of "heightened dynamic linking" beyond that of a DLL.

The following sections explain how to load and run an app image, how to create a shared library, and how to create and load an add-on image.


Loading an App Image

Loading an app image is like running a "sub-program." The image that you load is launched in much the same way as had you double-clicked it in the Browser, or launched it from the command line. It runs in its own team--it doesn't share the address space of the application from which it was launched--and, generally, leads its own life.

Any application can be loaded as an app image; you don't need to issue special compile instructions or otherwise manipulate the binary. The one requirement of an app image is that it must have a main() function; hardly a restrictive request.

To load an app image, you call the load_image() function, the protocol for which is:

thread_id load_image(int32 argc,
const char **argv,
const char **env)

The function's first two arguments identify the app image (file) that you want to launch--we'll return to this in a moment. Having located the file, the function creates a new team, spawns a main thread in that team, and then returns the thread_id of that thread to you. The thread that's returned is the executable's main thread. It won't be running: To make it run you pass the thread_id to resume_thread() or wait_for_thread() (as explained in the major section "Threads and Teams").

The argc/argv argument pair is copied and forwarded to the new thread's main() function:

The following example demonstrates a typical use of load_image(). First, we include the appropriate files and declare the necessary variables:

   #include <image.h>  /* load_executable() */
   #include <OS.h>     /* wait_for_thread() */
   #include <stdlib.h> /* malloc() */
    
   /* Declare the environ array. */
   extern char **environ;
   
   char **arg_v; /* choose a name that doesn't collide with argv */
   int32 arg_c; /* same here vis a vis argc */
   thread_id exec_thread;
   int32 return_value;

Install, in the arg_v array, the "command line" arguments. Let's pretend we're launching a program found in /boot/home/apps/adder that takes two integers, adds them together, and returns the result as main()'s exit code. Thus, there are three arguments: The name of the program, and the values of the two addends converted to strings. Since there are three arguments, we allocate arg_v to hold four pointers (to accommodate the final NULL). Then we allocate and copy the arguments.

   arg_c = 3;
   arg_v = (char **)malloc(sizeof(char *) * (arg_c + 1));
   
   arg_v[0] = strdup("/boot/home/apps/adder");
   arg_v[1] = strdup("5");
   arg_v[2] = strdup("3");
   arg_v[3] = NULL;

Now that everything is properly set up, we call load_image(). After the function returns, it's safe to free the allocated arg_v array:

   exec_thread = load_image(arg_c, arg_v, environ);
   free(arg_v);

At this point, exec_thread is suspended (the natural state of a newly-spawned thread). In order to retrieve its return value, we use wait_for_thread() to tell the thread to run:

   wait_for_thread(exec_thread, &return_value);

After wait_for_thread() returns, the value of return_value should be 8 (i.e. 5 + 3).


Creating a Shared Library

The primary documentation for creating a shared library is provided by MetroWerks in their CodeWarrior manual. Beyond the information that you find there, you should be aware of the following amendments and caveats.

You mustn't export your library's symbols through the -export all compiler flag. Instead, you should either use -export pragma or -@export filename (which is the same as -f filename). See the MetroWerks manual for details on how to use these flags.

The loader looks for libraries by following the LIBRARY_PATH environment variable.

The default library path looks like this:

   $ echo $LIBRARY_PATH
   %A/lib:/boot/home/config/lib:/boot/beos/system/lib

where "%A" means the directory that contains the app that the user is lauching.


Creating and Using an Add-on Image

An add-on image is indistinguishable from a shared library image. Creating an add-on is exactly like creating a shared library, a topic that we breezed through immediately above. The one difference is where the loader looks for add-ons:

The loader follows the trail given by the ADDON_PATH environment variable.

The default ADDON_PATH looks like this:

   $ echo $ADDON_PATH
   %A/add-ons:/boot/home/config/add-ons:/boot/beos/system/add-ons

Loading an Add-on Image

To load an add-on into your application, you call the load_add_on() function. The function takes a pathname (absolute or relative to the current working directory) to the add-on file, and returns an image_id number that uniquely identifies the image across the entire system.

For example, let's say you've created an add-on image that's stored in the file /boot/home/add-ons/adder. The code that loads the add-on would look like this:

   /* For brevity, we won't check errors.  */
   image_id addon_image;
   
   /* Load the add-on. */
   addon_image = load_add_on("/boot/home/add-ons/adder");

Unlike loading an executable, loading an add-on doesn't create a separate team, nor does it spawn another thread. The whole point of loading an add-on is to bring the image into your application's address space so you can call the functions and fiddle with the variables that the add-on defines.

Symbols

After you've loaded an add-on into your application, you'll want to examine the symbols (variables and functions) that it has brought with it. To get information about a symbol, you call the get_image_symbol() function:

status_t get_image_symbol(image_id image,
char *symbol_name,
int32 symbol_type,
void **location)

The function's first three arguments identify the symbol that you want to get:

The function returns, by reference in its final argument, a pointer to the symbol's address. For example, let's say the adder add-on code looks like this:

   int32 a1 = 0;
   int32 a2 = 0;
   
   int32 adder(void)
   {
      return (a1 + a2);
   }

To examine the variables (addend1 and addend2), you would call get_image_symbol() thus:

   int32 *var_a1, *var_a2; 
   
   get_image_symbol(addon_image, "a1", B_SYMBOL_TYPE_DATA, &var_a1);
   get_image_symbol(addon_image, "a2", B_SYMBOL_TYPE_DATA, &var_a2);

To get the symbol for the adder() function is a bit more complicated. The compiler mangles a function's name to encode the data types of the function's arguments. The encoding scheme is explained in the next section; to continue with the example, we'll simply accept that the adder() function's symbol is

   adder__Fv

And so...

   int32 (*func_add)();
   get_image_symbol(addon_image, "adder__Fv", B_SYMBOL_TYPE_TEXT, &func_add);

Now that we've retrieved all the symbols, we can set the values of the two addends and call the function:

   *var_a1 = 5;
   *var_a2 = 3;
   int32 return_value = (*func_add)();

Function Symbol Encoding

The compiler encodes function symbols according to this format:

function__<Nclass>F<arg1><arg2><arg3>....

function is thename of the function; some C++ names are special:

The <Nclass> symbol is used only if the function is a member of a class; N is the length (in characters) of the class name and class is the name itself.

The optional <argN> symbols encode the argument types:

Code Type
i int
s short
l long
f float
d double
c char
v void

In addition, if the argument is declared as unsigned, the type code character is preceded by "U". If it's a pointer, the type code (and, potentially, the "U") is preceded by "P"; a pointer to a pointer is preceded by "PP". For example, a function that's declared as

   void Func(int32, unsigned char **, float *, double);

would have the following symbol name:

   Func__FlUPPcPfd

Note that typedef's are translated to their natural types. So, for example, this:

   void dump_thread(thread_id, bool);

becomes

   dump_thread__FlUc

But There's an Easier Way

There's actually an easier, if less elegant, way to find a function's compiler-mangled name:

Use pefdump.

pefdump the add-on that you want to use, and then copy the function name from the "export symbol table:" section. As an example, let's pefdump the Device Kit library, libdevice.so:

   $ cd /system/lib
   $ pefdump libdevice.so
    
   /* A LOT of output; and then, at the end... */
      
    export symbol table:
       class                                  name  value
       TVECT                        Close__4BA2DFv  10002260
       TVECT                   Close__9BJoystickFv  10002220
       TVECT                 __dt__11BSerialPortFv  10002180
       TVECT                IsDSR__11BSerialPortFv  100020c0
       TVECT                         __ct__4BA2DFv  10002280
       TVECT                         __dt__4BA2DFv  10002278
       TVECT               Close__12BDigitalPortFv  100021d8
       TVECT           ParityMode__11BSerialPortFv  10002110
   ...


Image Functions


get_image_info(), get_next_image_info(), image_info


      status_t get_image_info(image_id image, image_info *info)

      status_t get_next_image_info(team_id team, 
         int32 *cookie, 
         image_info *info)

      struct {} image_info

These functions copy, into the info argument, the image_info structure for a particular image. The get_image_info() function gets the information for the image identified by image.

The get_next_image_info() function lets you step through the list of a team's images through iterated calls. The team argument identifies the team you want to look at; a team value of 0 means the team of the calling thread. The cookie argument is a placemark; you set it to 0 on your first call, and let the function do the rest. The function returns B_BAD_VALUE when there are no more images to visit:

   /* Get the image_info for every image in this team. */
   image_info info;
   int32 cookie = 0;
   
   while (get_next_image_info(0, &cookie, &info) == B_OK)
      ...

The image_info structure is defined as:


      typedef struct {
            image_id id;
             image_type  type;
            int32  sequence; 
            int32  init_order;
            B_PFV init_routine;
            B_PFV term_routine;
            dev_t device;
            ino_t node;
             char  name[MAXPATHLEN]; 
             void  *text;     
              void  *data;     
            int32  text_size;    
             int32  data_size;   
         } image_info

The fields are:

The self-explanatory image_type constants are:

RETURN CODES


get_image_symbol(), get_nth_image_symbol()


      status_t get_image_symbol(image_id image,
         char *symbol_name,
         int32 symbol_type,
         void **location)

      status_t get_nth_image_symbol(image_id image, 
         int32 n, 
         char *name,
         int32 *name_length,
         int32 *symbol_type,
         void **location)

get_image_symbol() returns, in location, a pointer to the address of the symbol that's identified by the image, symbol_name, and symbol_type arguments. An example demonstrating the use of this function is given in "Symbols."

get_nth_image_symbol() returns information about the n'th symbol in the given image. The information is returned in the arguments:

Keep in mind that name_length is reset each time you call get_nth_image_symbol(). If you're calling the function iteratively (to retrieve all the symbols in an image), you need to reset the name_length value between calls.

To retrieve image_id numbers on which these functions can act, use the get_next_image_info() function. Such numbers are also returned directly when you load an add-on image through the load_add_on() function.

RETURN CODES


load_add_on(), unload_add_on()


      image_id load_add_on(const char *pathname)
      status_t unload_add_on(image_id image)

load_add_on() loads an add-on image, identified by pathname, into your application's address space.

An example that demonstrates the use of load_add_on() is given in "Loading an Add-on Image."

You can load the same add-on image twice; each time you load the add-on a new, unique image_id is created and returned.

unload_add_on() removes the add-on image identified by the argument. The image's symbols are removed, and the memory that they represent is freed. If the argument doesn't identify a valid image, the function returns B_ERROR. Otherwise, it returns B_NO_ERROR.

RETURN CODES


load_image()


      thread_id load_image(int argc,
         const char **argv,
         const char **env)

Loads an app image into the system (it doesn't load the image into the caller's address space), creates a separate team for the new application, and spawns and returns the ID of the team's main thread. The image is identified by the pathname given in argv[0].

The arguments are passed to the image's main() function (they show up there as the function's similarly named arguments):

      extern char **environ;
      
      load_image(..., environ);

The argv and envp arrays are copied into the new thread's address space. If you allocated either of these arrays, it's safe to free them immediately after load_image() returns.

The thread that's returned by load_image() is in a suspended state. To start the thread running, you pass the thread_id to resume_thread() or wait_for_thread().

An example that demonstrates the use of load_image() is given in "Loading an App Image."

RETURN CODES






The Be Book, in lovely HTML, for the BeOS Preview Release.

Copyright © 1997 Be, Inc. All rights reserved.

Be is a registered trademark; BeOS, BeBox, BeWare, GeekPort, the Be logo, and the BeOS logo are trademarks of Be, Inc.

Last modified July 10, 1997.