Linux Programming Topics


Linux Programming Topics

Developing software under Linux is quite similar to developing software under any UNIX system. Most C and UNIX programming issues are generic and apply to all UNIX systems. There are, however, a few topics you want to know about if you are developing software for Linux.

This section covers the most significant topic: the Executable and Linking Format (ELF) binary in Linux. The other topic-the use of dynamic linking in applications-is related to ELF. Also, I describe how you can exploit dynamic linking and how you can create a dynamically linked library in Linux.

Understanding the Executable and Linking Format

If you have programmed in UNIX, you probably know that when you compile and link a program, the default executable file is named a.out. What you may not have realized is that a file format is associated with the a.out file. The operating system has to know this file format so it can load and run an executable. In the early days, Linux used the a.out format for its binaries.

Although the a.out format has served its purpose adequately, it has two shortcomings:

  • Shared libraries are difficult to create.

  • Dynamically loading a shared library is cumbersome.

Using shared libraries is desirable because a shared library enables many executable programs to share the same block of code. Also, the dynamic loading of modules is becoming increasingly popular because it enables an application to load blocks of code only when needed, thus reducing the memory requirement of the application.

If you want to check the binary format of an executable file, use the file command. The following example shows how to check the file type of /bin/ls (the executable file for the ls command):

file /bin/ls
/bin/ls: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux
 2.2.5, dynamically linked (uses shared libs), stripped

On the other hand, the file command reports the following for an older a.out format executable (the default name of executable files is still a.out, but the format is ELF):

file a.out
a.out: Linux/i386 demand-paged executable (QMAGIC)

Using Shared Libraries in Linux Applications

Most Linux programs use shared libraries. At minimum, most C programs use the C shared library libc.so.X , wherein X is a version number. When a program uses one or more shared libraries, you need the program's executable file, as well as all the shared libraries, to run the program. In other words, your program won't run if all shared libraries are not available on a system.

If you sell an application, you need to make sure all necessary shared libraries are distributed with your software.

Examining Shared Libraries that a Program Uses

Use the ldd utility to determine which shared libraries an executable program needs. The following is what ldd reports for a typical C program that uses the ELF binary format:

ldd a.out
   libc.so.6 => /lib/tls/libc.so.6 (0x42000000)
   /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)

For a more complex program, such as gimp (ELF version), ldd shows more shared libraries:

ldd /usr/bin/gimp
   libgtk-1.2.so.0 => /usr/lib/libgtk-1.2.so.0 (0x40029000)
   libgdk-1.2.so.0 => /usr/lib/libgdk-1.2.so.0 (0x40171000)
   libgmodule-1.2.so.0 => /usr/lib/libgmodule-1.2.so.0 (0x401a9000)
   libglib-1.2.so.0 => /usr/lib/libglib-1.2.so.0 (0x401ac000)
   libdl.so.2 => /lib/libdl.so.2 (0x401d1000)
   libXi.so.6 => /usr/X11R6/lib/libXi.so.6 (0x401d4000)
   libXext.so.6 => /usr/X11R6/lib/libXext.so.6 (0x401dd000)
   libX11.so.6 => /usr/X11R6/lib/libX11.so.6 (0x401eb000)
   libm.so.6 => /lib/tls/libm.so.6 (0x402ca000)
   libc.so.6 => /lib/tls/libc.so.6 (0x42000000)
   /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)

In this case, the program uses several shared libraries, including the X11 library (libX11.so.6), the GIMP toolkit (libgtk-1.2.so.0), the General Drawing Kit (GDK) library (libgdk-1.2.so.0), the Math library (libm.so.6), and the C library (libc.so.6).

Thus, almost any Linux application requires shared libraries to run. In addition, the shared libraries must have the same binary format an application uses.

Creating a Shared Library

With ELF, creating a shared library for your own application is simple enough. Suppose you want to implement an object in the form of a shared library. A set of functions in the shared library represents the object's interfaces. To use the object, load its shared library, and invoke its interface functions (you learn how to do this in the following section).

Here is the C source code for this simple object, implemented as a shared library (you might also call it a dynamically linked library)-save this in a file named dynobj.c:

/*-----------------------------------------------------*/
/* File: dynobj.c
 *
 * Demonstrate use of dynamic linking.
 * Pretend this is an object that can be created by calling
 * init and destroyed by calling destroy.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* Data structure for this object */
typedef struct OBJDATA
{
  char *name;
  int version;
} OBJDATA;

/*-----------------------------------------------------*/
/* i n i t
 *
 * Initialize object (allocate storage).
 *
 */
void* init(char *name)
{
  OBJDATA *data = (OBJDATA*)calloc(1, sizeof(OBJDATA));
  if(name)
    data->name = malloc(strlen(name)+1);
  strcpy(data->name, name);

  printf("Created: %s\n", name);

  return data;
}
/*-----------------------------------------------------*/
/* s h o w
 *
 * Show the object.
 *
 */
void show(void *data)
{
  OBJDATA *d = (OBJDATA*)data;
  printf("show: %s\n", d->name);
}
/*-----------------------------------------------------*/
/* d e s t r o y
 *
 * Destroy the object (free all storage).
 *
 */
void destroy(void *data)
{
  OBJDATA *d = (OBJDATA*)data;
  if(d)
  {
    if(d->name)
    {
      printf("Destroying: %s\n", d->name);
      free(d->name);
    }
    free(d);
  }
}

The object offers three interface functions:

  • init to allocate any necessary storage and initialize the object

  • show to display the object (here, it simply prints a message)

  • destroy to free any storage

To build the shared library named libdobj.so, follow these steps:

  1. Compile all source files with the -fPIC flag. In this case, compile the dynobj.c file by using this command:

    gcc -fPIC -c dynobj.c
  2. Link the objects into a shared library with the -shared flag, and provide appropriate flags for the linker. To create the shared library named libdobj.so.1, use the following:

    gcc -shared -Wl,-soname,libdobj.so.1 -o libdobj.so.1.0 dynobj.o
    
  3. Set up a sequence of symbolic links so that programs that use the shared library can refer to it with a standard name. For the sample library, the standard name is libdobj.so, and the symbolic links are set up by using these commands:

    ln -sf libdobj.so.1.0 libdobj.so.1
    ln -sf libdobj.so.1 libdobj.so
  4. When you test the shared library, define and export the LD_LIBRARY_PATH environment variable by using the following command:

    export LD_LIBRARY_PATH=`pwd`:$LD_LIBRARY_PATH

After you test the shared library and are satisfied that the library works, copy it to a standard location, such as /usr/local/lib, and run the ldconfig utility to update the link between libdobj.so.1 and libdobj.so.1.0. These are the commands you use to install your shared library for everyone's use (you have to be root to perform these steps):

cp libdobj.so.1.0 /usr/local/lib
/sbin/ldconfig
cd /usr/local/lib
ln -s libdobj.so.1 libdobj.so

Dynamically Loading a Shared Library

ELF makes it simple to load a shared library in your program and to use the functions within the shared library. The header file <dlfcn.h> declares the functions for loading and using a shared library. Four functions are declared in the file dlfcn.h for dynamic loading:

  • void *dlopen(const char *filename, int flag);-loads the shared library specified by filename and returns a handle for the library. The flag can be RTD_LAZY (resolve undefined symbols as the library's code is executed); or RTD_NOW (resolve all undefined symbols before dlopen returns and fail if all symbols are not defined). If dlopen fails, it returns NULL.

  • const char *dlerror (void);-if dlopen fails, call dlerror to get a string that contains a description of the error.

  • void *dlsym (void *handle, char *symbol);-returns the address of the specified symbol (function name) from the shared library identified by the handle (that was returned by dlopen).

  • int dlclose (void *handle);-unloads the shared library if no one else is using it.

Here is the dlfcn.h file with the appropriate function declarations:

/*-----------------------------------------------------*/
/* File: dlfcn.h
 *
 * Header file with function prototypes.
 */
void *dlopen(const char *filename, int flag); 
const char *dlerror (void);
void *dlsym (void *handle, char *symbol);
int dlclose (void *handle);

When you use any of these functions, include the header file <dlfcn.h> with this preprocessor directive:

#include <dlfcn.h>

The following is a simple test program-dltest.c-that shows how to load and use the object defined in the shared library libdobj.so, which you create in the preceding section:

/*-----------------------------------------------------*/
/* File: dltest.c
 *
 * Test dynamic linking.
 *
 */
#include <dlfcn.h>  /* For the dynamic loading functions */
#include <stdio.h>

int main(void)
{
  void *dlobj;
  void * (*init_call)(char *name);
  void (*show_call)(void *data);
  void (*destroy_call)(void *data);

/* Open the shared library and set up the function pointers */
  if(dlobj = dlopen("libdobj.so.1",RTLD_LAZY))
  {
    void *data;

    init_call=dlsym(dlobj,"init");
    show_call=dlsym(dlobj,"show");
    destroy_call=dlsym(dlobj,"destroy");

/* Call the object interfaces */
    data = (*init_call)("Test Object");
    (*show_call)(data);
    (*destroy_call)(data);
  }
  return 0;
}

The program is straightforward: it loads the shared library, gets the pointers to the functions in the library, and calls the functions through the pointers.

You can compile and link this program in the usual way, but you must link with the -ldl option so you can use the functions declared in <dlfcn.h>. Here is how you build the program dltest:

gcc -o dltest dltest.c -ldl

To see the program in action, run dltest:

./dltest
Created: Test Object
show: Test Object
Destroying: Test Object

Although this demonstration is not exciting, you now have a sample program that uses a shared library.

To see the benefit of using a shared library, return to the preceding section, and make some changes in the shared library (print some other message in a function, for example). Rebuild the shared library alone. Then run dltest again. The resulting printout should show the effect of the changes you make in the shared library, which means you can update the shared library independently of the application. You should also note that a change in a shared library can affect many applications installed on your system. Therefore, you should be careful when making changes to any shared library.