In my last blog I showed how the LIBPATH gets used and also how the runtime search path in the binary gets used. LIBPATH is obviously set as an environment variable, but how does that runtime search path get generated? Let's explore!

#include <stdio.h>

int main(int argc, char** argv)
{
  printf("Hello world!\n");
  return 0;
}

Above is a pretty standard "Hello World" program in C. Let's compile it and look at its search path

$ gcc -o hello hello.c

$ dump -H hello

hello:

                        ***Loader Section***
                      Loader Header Information
VERSION#         #SYMtableENT     #RELOCent        LENidSTR
0x00000001       0x0000000b       0x0000002b       0x000000d3

#IMPfilID        OFFidSTR         LENstrTBL        OFFstrTBL
0x00000002       0x0000032c       0x00000039       0x000003ff


                        ***Import File Strings***
INDEX  PATH                          BASE                MEMBER
0      /QOpenSys/opt/freeware/bin/../lib/gcc/powerpc-ibm-aix7.1.0.0/4.8.3:/QOpenSys/opt/freeware/bin/../lib/gcc:/QOpenSys/opt/freeware/bin/../lib/gcc/powerpc-ibm-aix7.1.0.0/4.8.3/../../..:/usr/lib:/lib
1                                    libc.a              shr.o

What the what? Where did all that junk come from?

The default behavior for the linker (ld) is to add whatever directories are passed to the linker to find libraries (via the -L argument) as well as the default search path (/usr/lib:/lib). We can see that GCC is automatically adding some of its own library directories to be searched whenever you tell it to compile a binary:

  • /QOpenSys/opt/freeware/bin/../lib/gcc/powerpc-ibm-aix7.1.0.0/4.8.3
  • /QOpenSys/opt/freeware/bin/../lib/gcc
  • /QOpenSys/opt/freeware/bin/../lib/gcc/powerpc-ibm-aix7.1.0.0/4.8.3/../../..

If we canonicalize the relative paths, they then become:

  • /QOpenSys/opt/freeware/lib/gcc/powerpc-ibm-aix7.1.0.0/4.8.3
  • /QOpenSys/opt/freeware/lib/gcc
  • /QOpenSys/opt/freeware/lib

"It's a bypass! Gotta build bypasses!"

Let's bypass GCC and link the binary ourselves:

$ gcc -c -o hello.o hello.c

$ ld -o hello hello.o /QOpenSys/usr/lib/crt0.o -lc

We have to link in crt0.o as we define a main() function, but the default entry point for a program is __start on AIX, so there's a special glue module that does the necessary setup from the __start entry point to call main(). We also need to link with libc, to get access to printf. Let's look at the dump output now:

$ dump -H hello

hello:

                        ***Loader Section***
                      Loader Header Information
VERSION#         #SYMtableENT     #RELOCent        LENidSTR
0x00000001       0x00000008       0x00000010       0x0000001e

#IMPfilID        OFFidSTR         LENstrTBL        OFFstrTBL
0x00000002       0x000001a0       0x00000039       0x000001be


                        ***Import File Strings***
INDEX  PATH                          BASE                MEMBER
0      /usr/lib:/lib
1                                    libc.a              shr.o

See now it has only added the default search path, since we did not pass any additional -L arguments to ld. Let's see what happens when we add a path:

$ ld -o hello hello.o /QOpenSys/usr/lib/crt0.o -lc -L/this/path/is/bad/and/you/should/feel/bad

$ dump -H hello

hello:

                        ***Loader Section***
                      Loader Header Information
VERSION#         #SYMtableENT     #RELOCent        LENidSTR
0x00000001       0x00000008       0x00000010       0x00000048

#IMPfilID        OFFidSTR         LENstrTBL        OFFstrTBL
0x00000002       0x000001a0       0x00000039       0x000001e8


                        ***Import File Strings***
INDEX  PATH                          BASE                MEMBER
0      /this/path/is/bad/and/you/should/feel/bad:/usr/lib:/lib
1                                    libc.a              shr.o

Boom! We added a path to the runtime search path. Sometimes you really don't want this, though. Let's say you're compiling a library and an application. The library is called libfoo.a and the binary is called foo. While building, you first build libfoo.a in to the current directory, then you go to build foo. Since foo needs to link to libfoo, you need to tell ld where to find it during build time. You might do something like this:

$ ld -o foo foo.o /QOpenSys/usr/lib/crt0.o -L. -lfoo -lc

$ dump -H foo

foo:

                        ***Loader Section***
                      Loader Header Information
VERSION#         #SYMtableENT     #RELOCent        LENidSTR
0x00000001       0x00000008       0x00000010       0x00000048

#IMPfilID        OFFidSTR         LENstrTBL        OFFstrTBL
0x00000002       0x000001a0       0x00000039       0x000001e8


                        ***Import File Strings***
INDEX  PATH                          BASE                MEMBER
0      .:/usr/lib:/lib
1                                    libc.a              shr.o
2                                    libfoo.a            shr.o

Now when you install the application, you copy foo in to /usr/bin and libfoo.a in to /usr/lib. You run your application and everything works great (since libfoo.a gets loaded from /usr/lib/libfoo.a via the runtime path). Now though, you're doing some more development and you run foo but it gives a load error about missing symbols. What gives? Well, the directory you're in happens to have an old libfoo.a and is found first via ./libfoo.a due to '.' being on the runtime search path before /usr/lib! That's no good! Heck, that could even be a security issue if your application could be tricked in to loading a malicious library that the attacker tricks you in to uploading.

So lets say we don't want to include these paths in our finished binary? How do we get rid of them. Well, ld has an option -bnolibpath that takes care of that:

$ ld -o foo foo.o /QOpenSys/usr/lib/crt0.o -bnolibpath -L. -lfoo -lc

$ dump -H foo

foo:

                        ***Loader Section***
                      Loader Header Information
VERSION#         #SYMtableENT     #RELOCent        LENidSTR
0x00000001       0x00000008       0x00000010       0x00000048

#IMPfilID        OFFidSTR         LENstrTBL        OFFstrTBL
0x00000002       0x000001a0       0x00000039       0x000001e8


                        ***Import File Strings***
INDEX  PATH                          BASE                MEMBER
0      /usr/lib:/lib
1                                    libc.a              shr.o
2                                    libfoo.a            shr.o

One thing to know about the -bnolibpath option is that it isn't hard-coded to /usr/lib:/lib, but will use the value of the LIBPATH environment variable if it is set:

$ LIBPATH=/QOpenSys/usr/lib ld -o foo foo.o /QOpenSys/usr/lib/crt0.o -bnolibpath -L. -lfoo -lc

$ dump -H foo

foo:

                        ***Loader Section***
                      Loader Header Information
VERSION#         #SYMtableENT     #RELOCent        LENidSTR
0x00000001       0x00000008       0x00000010       0x00000048

#IMPfilID        OFFidSTR         LENstrTBL        OFFstrTBL
0x00000002       0x000001a0       0x00000039       0x000001e8


                        ***Import File Strings***
INDEX  PATH                          BASE                MEMBER
0      /QOpenSys/usr/lib
1                                    libc.a              shr.o
2                                    libfoo.a            shr.o

Or you can use the -blibpath option to explicitly set it:

$ ld -o foo foo.o /QOpenSys/usr/lib/crt0.o -L. -lfoo -lc -blibpath:/opt/mypath:/QOpenSys/usr/lib

$ dump -H foo

foo:

                        ***Loader Section***
                      Loader Header Information
VERSION#         #SYMtableENT     #RELOCent        LENidSTR
0x00000001       0x00000008       0x00000010       0x00000048

#IMPfilID        OFFidSTR         LENstrTBL        OFFstrTBL
0x00000002       0x000001a0       0x00000039       0x000001e8


                        ***Import File Strings***
INDEX  PATH                          BASE                MEMBER
0      /opt/mypath:/QOpenSys/usr/lib
1                                    libc.a              shr.o
2                                    libfoo.a            shr.o

There's one other thing to know: you can also hard-code the path to a specific library so that it will not use the search path at all. You do this by passing the full path to the library as an input to ld instead of having ld find it via the -l option:

$ ld -o hello hello.o /QOpenSys/usr/lib/crt0.o /QOpenSys/usr/lib/libc.a

$ dump -H hello

hello:

                        ***Loader Section***
                      Loader Header Information
VERSION#         #SYMtableENT     #RELOCent        LENidSTR
0x00000001       0x00000008       0x00000010       0x00000048

#IMPfilID        OFFidSTR         LENstrTBL        OFFstrTBL
0x00000002       0x000001a0       0x00000039       0x000001e8


                        ***Import File Strings***
INDEX  PATH                          BASE                MEMBER
0      /usr/lib:/lib
1      /QOpenSys/usr/lib             libc.a              shr.o

You can see above that the PATH field is set for index 2, so now libc.a will always be loaded from /QOpenSys/usr/lib/libc.a and will ignore the runtime search path as well as the LIBPATH environment variable. If that's not what you want, you can tell the linker to ignore qualified library paths given on the command line and always search for these at runtime with the -bnoipath option:

$ ld -o hello hello.o /QOpenSys/usr/lib/crt0.o -bnoipath /QOpenSys/usr/lib/libc.a

$ dump -H hello

hello:

                        ***Loader Section***
                      Loader Header Information
VERSION#         #SYMtableENT     #RELOCent        LENidSTR
0x00000001       0x00000008       0x00000010       0x00000048

#IMPfilID        OFFidSTR         LENstrTBL        OFFstrTBL
0x00000002       0x000001a0       0x00000039       0x000001e8


                        ***Import File Strings***
INDEX  PATH                          BASE                MEMBER
0      /usr/lib:/lib
1                                    libc.a              shr.o

Ok, so we've learned about the -bnolibpath, -blibpath, and -bnoipath ld options, but most of the time we're not going to be calling ld directly, but instead we let our compiler handle calling the linker. How do we tell gcc to customize these options? Well, gcc has a way to pass linker flags to ld via the -Wl, option. Just prefix the linker option with -Wl, and pass it to gcc:

$ gcc -o hello hello.c -Wl,-bnolibpath

$ dump -H hello

hello:

                        ***Loader Section***
                      Loader Header Information
VERSION#         #SYMtableENT     #RELOCent        LENidSTR
0x00000001       0x00000008       0x00000010       0x00000048

#IMPfilID        OFFidSTR         LENstrTBL        OFFstrTBL
0x00000002       0x000001a0       0x00000039       0x000001e8


                        ***Import File Strings***
INDEX  PATH                          BASE                MEMBER
0      /usr/lib:/lib
1                                    libc.a              shr.o

And that's all I have to say about that. (For now?)

So what's the takeaway from all this?

I recommend using the -blibpath option to explicitly set your runtime library search path. This prevents GCC from adding weird directories to your search path, potentially problematic paths like '.' to your search path, but prevents your users from having to set the LIBPATH to find your stuff thus relegating LIBPATH for when a user installs the library in to a different path or you need to override an installed library with your own for debug/test reasons.