Pthread stubs in C library - Release Notes

 

Problem

 

On HP-UX if a non-threaded application links to a thread-safe library, calls to thread safe routines from the application fail at run time due to unresolved symbols of the form of pthread_*. To resolve these symbols it is necessary to link the non-threaded application to a threads library (libpthread or libcma). But linking to that library forces the application to use thread-safe features even if it creates no threads, with subsequent loss of performance.

Resolution

Due to the problems stated above, HP has decided to implement POSIX 1x thread stubs.

Providing POSIX 1c thread stubs in HP-UX C language library have two direct effects for non-threaded applications:

a) POSIX 1c thread symbols are resolved if a non-threaded application links to a thread-safe library.

b) We avoid the overhead of a real thread library especially the overhead associated with mutexes when the non-threaded application uses thread stubs rather than the real thread library procedures.

 

As per the libc cumulative patches, PHCO_22923 (11.00) and PHCO_23772 (11.11), the libc shared library contains stubs for the pthread_* functions in libpthread and libcma.  The stubs allow non-threaded applications to dynamically load thread-safe libraries successfully, so that the pthread symbols are resolved. Applications that resolves pthread/cma calls to the stub must be built without -lpthread or -lcma on the link line.

 

Stubs provided in libc do not have any functionality, these are dummy functions returning zero except pthread_getspecific() family of APIs which has full functionality implemented in the stubs.

 

List of pthread calls for which the stubs are provided in the C library is given below.

 

The pthread calls to any of the above stub returns zero.

 

Exceptions

 

The stub for the following pthread calls has full functionality. More information about their functionalities is in the pthread (3T) man pages.

 

Calls to the stub,

Link Order problems:

 

An application may inadvertently pick up the stubs present in libc when it is intended to use the real pthread APIs, or cma APIs. These are link order problems. An application that needs cma behavior must link to libcma and must do so in the supported link order, i.e. the link line should only be shared and not contain -lc before -lcma.

 

So long as this condition is met, the correct cma functions will be referenced. Similarly, a multithreaded application that needs pthread library behavior must link to libpthread and must do so in a supported link order, and only use shared libc and libpthread.

 

Examples of Potential link order problems:

Example 1

 

The applications or any library linked that will resolve pthread/cma calls to the stubs must be built without -lpthread or -lcma on the link line. If you specify -lc before -lpthread, your application will use the pthread stubs in libc, but other problems may occur as given in the examples below:

 

$ cat thread.c

#include <pthread.h>

#include <stdio.h>

 

void *thread_nothing(void *p)

{

printf("Success\n");

}

 

int main()

{

    int err;

    pthread_t thrid;

 

    err = pthread_create(&thrid, (pthread_attr_t *) NULL, thread_nothing,

                         (void *) NULL);

    sleep(1);

    if (err) {

      printf("Error\n");

      return err;

    }

}

 

$ cc thread.c -lc -lpthread

$ a.out

Error

$ chatr a.out

a.out:

         shared executable

         shared library dynamic path search:

             SHLIB_PATH     disabled  second

             embedded path  disabled  first  Not Defined

         shared library list:

             dynamic   /usr/lib/libc.2  <- libc before libpthread

             dynamic   /usr/lib/libpthread.1

         shared library binding:

             deferred

         global hash table disabled

 

$ cc thread.c -lpthread

$ a.out

Success

$ chatr a.out

a.out:

         shared executable

         shared library dynamic path search:

             SHLIB_PATH     disabled  second

             embedded path  disabled  first  Not Defined

         shared library list:

             dynamic   /usr/lib/libpthread.1

             dynamic   /usr/lib/libc.2

         shared library binding:

             deferred

         global hash table disabled ...

Example 2

 

Specifying -lc before -lpthread in threaded applications can cause run-time problems as in the following example. Because the pthread/cma stubs are resolved instead of the real pthread/cma functions:

 

 

$ cat a.c

     #include <stdio.h>

     #include <dl.h>

 

     extern int errno;

 

     main()

     {

             shl_load("/usr/lib/librt.2", BIND_DEFERRED, 0);

             printf("Error %d, %s\n", errno, strerror(errno));

     }

 

$ cc a.c -lc -lpthread

$ a.out

Error 22, Invalid argument

$ LD_PRELOAD=/usr/lib/libpthread.1 ./a.out

Error 0, Error 0

 

$ cat  b.c

     #include <stdio.h>

     #include <dlfcn.h>

 

     void* handle;

     extern int errno;

 

     main()

     {

             handle = dlopen("lib_not_found", RTLD_LAZY);

             printf("Error %d, %s\n", errno, strerror(errno));

             if (handle == NULL) {

               printf("Error: %s\n",dlerror());

             }

     }

$ cc b.c -lc -lpthread

$ a.out

Error 22, Invalid argument

Error:

$ LD_PRELOAD=/usr/lib/libpthread.1 ./a.out

Error 0, Error 0

Error: Can't open shared library: lib_not_found

 

Therefore, -lc should never be specified in the build command of an executable or shared library.

 

By default, the compiler drivers (cc, aCC, f90) automatically pass –lc to the linker at the end of the link line of the executables. It is not necessary to specify -lc when building a shared library because libc will be resolved by the reference to libc in the executable build command. The libc will be resolved by -lc that is automatically passed by the compiler drivers to the linker.

 

To see if a shared library was built with -lc, look at the shared library list in the chatr()  output, or list the dependent libraries with ldd(1):

 

$ cc +z -c lib1.c       

$ ld -b -o lib1.sl lib1.o -lc

$ ldd lib1.sl

        /usr/lib/libc.2 =>      /usr/lib/libc.2

        /usr/lib/libdld.2 =>    /usr/lib/libdld.2

        /usr/lib/libc.2 =>      /usr/lib/libc.2

 

$ cc +DA2.0W +z -c lib1.c

$ ld -b -o lib1.sl lib1.o -lc

$ ldd lib1.sl 

        libc.2 =>       /lib/pa20_64/libc.2

        libdl.1 =>      /usr/lib/pa20_64/libdl.1

 

To see the order in which dependent shared libraries will be loaded at run-time (order is only valid in 64-bit mode), use ldd(1) on the executable (ldd in 32-bit mode displays the order in which  libraries are loaded in reverse order):

 

$ cc +DA2.0W thread.c -lpthread

$ ldd a.out

        libpthread.1 => /usr/lib/pa20_64/libpthread.1

        libc.2 =>       /usr/lib/pa20_64/libc.2

        libdl.1 =>      /usr/lib/pa20_64/libdl.1

$ cc +DA2.0W thread.c -lc -lpthread

$ ldd a.out

        libc.2 =>       /usr/lib/pa20_64/libc.2

        libpthread.1 => /usr/lib/pa20_64/libpthread.1

        libdl.1 =>      /usr/lib/pa20_64/libdl.1

$ cc +DA2.0W thread.c -lpthread -lc

$ ldd a.out

        libpthread.1 => /usr/lib/pa20_64/libpthread.1

        libc.2 =>       /usr/lib/pa20_64/libc.2

        libdl.1 =>      /usr/lib/pa20_64/libdl.1

 

Recommendations:

 

·        remove -lc from the build command of all shared libraries

·        remove -lc from the build command of all executables

·        use the LD_PRELOAD environment variable set to the full pathname for libpthread or libcma, which will cause the library to be loaded at program startup before other dependent libraries. LD_PRELOAD functionality is available in PHSS_22478 and later Linker patches. See the dld.sl(5) man page.

·        if you link directly with ld(1) instead of with a compiler driver, add -lc as the last component on the link line.

 

Example 1 (64-bit):

 

If a 64-bit shared library is built with -lpthread but the executable is not, libc is loaded before libpthread (due to breadth-first searching), and the pthread calls are resolved to the pthread stubs in libc. At run-time, after the a.out is loaded, the dependencies of a.out are loaded in breadth-first order:  libc is loaded as a dependent of a.out before libpthread is loaded as a dependent of libc.2. The dependency list of the first case is:

 

                     a.out

                  /     /   \

                lib1  lib2  libc

                  |     |

                libc  libpthread

 

Therefore the load graph is constructed as:  lib1.sl --> lib2.sl -->libc.2 --> libpthread.1

 

This is the desired behavior for non-threaded applications, but causes threaded applications (that use either libpthread or libcma) to fail.

 

# lib1.sl specifies -lc; lib2.sl specifies  -lpthread;
no -lpthread on a.out

 

$ cc -c +z +DA2.0W lib1.c lib2.c

lib1.c:

lib2.c:

$ ld -b -o lib1.sl -lc lib1.o

$ ld -b -o lib2.sl -lpthread lib2.o

$ cc +DA2.0W thread.c -L. -l1 -l2

$ a.out

Error

$ ldd a.out

        lib1.sl =>      ./lib1.sl

        lib2.sl =>      ./lib2.sl

        libc.2 =>       /usr/lib/pa20_64/libc.2

        libc.2 =>       /lib/pa20_64/libc.2

        libpthread.1 => /lib/pa20_64/libpthread.1

        libdl.1 =>      /usr/lib/pa20_64/libdl.1

 

# lib2.sl specifies -lpthread; no -lpthread on a.out

$ ld -b -o lib1.sl lib1.o

$ ld -b -o lib2.sl -lpthread lib2.o

$ cc +DA2.0W thread.c -L. -l1 -l2

$ a.out

Error

$ ldd a.out

        lib1.sl =>      ./lib1.sl

        lib2.sl =>      ./lib2.sl

        libc.2 =>       /usr/lib/pa20_64/libc.2

        libpthread.1 => /lib/pa20_64/libpthread.1

        libdl.1 =>      /usr/lib/pa20_64/libdl.1

 

The same problem will occur if -lcma is listed as a dependent library of a shared library, and you would need to link the executable with -lcma.

Solution:

 

For threaded applications, run the executable with LD_PRELOAD set to the libpthread library or link the executable with -lpthread:

 

# use LD_PRELOAD to load libpthread first

$ ld -b -o lib1.sl lib1.o

$ ld -b -o lib2.sl -lpthread lib2.o

$ cc +DA2.0W thread.c -L. -l1 -l2

$ a.out

Error

$ ldd a.out

        lib1.sl =>      ./lib1.sl

        lib2.sl =>      ./lib2.sl

        libc.2 =>       /usr/lib/pa20_64/libc.2

        libpthread.1 => /lib/pa20_64/libpthread.1

        libdl.1 =>      /usr/lib/pa20_64/libdl.1

$ LD_PRELOAD="/lib/pa20_64/libpthread.1" a.out

Success

 

# a.out correctly lists -lpthread for a threaded application

$ ld -b -o lib1.sl lib1.o

$ ld -b -o lib2.sl -lpthread lib2.o

$ cc +DA2.0W thread.c -L. -l1 -l2 -lpthread

$ a.out

Success

$ ldd a.out

        lib1.sl =>      ./lib1.sl

        lib2.sl =>      ./lib2.sl

        libpthread.1 => /usr/lib/pa20_64/libpthread.1

        libc.2 =>       /usr/lib/pa20_64/libc.2

        libpthread.1 => /lib/pa20_64/libpthread.1

        libdl.1 =>      /usr/lib/pa20_64/libdl.1

Example 2 (archived libc):

 

If the link line of your shared library contains -lc to explicitly link in libc, remove –lc.  Otherwise, shared libraries may be referencing libc.2 while the a.out may reference some older (archived) libc version. Thus the application will actually be using two different versions of libc and possibly mixing code. This may cause compatibility problems. Basically, an application or library should never directly link against libc. All programs need to be linked against libc (which the compiler does automatically), so a shared library will always have the interfaces it needs to execute properly without needing to specify -lc on the link line.