Linux USES GCC to compile the c Shared library steps

  • 2020-04-02 02:09:16
  • OfStack

Libraries are essential to any programmer. A library is code that has been compiled for you to use. They often provide some general functions, such as linked lists and binary trees that can be used to hold any data, or a specific function such as an interface to a database server, like MySQL.

Most large software projects contain several components, some of which you find useful in other projects, or you separate out different components for organizational purposes only. When you have a set of reusable and logical functions, it is useful to build them into a library so that you do not copy the source code into your source code and compile them again each time. In addition, you can ensure that each module of your program is isolated, so that when you modify one module, it will not affect the other modules. Once you've written a module and passed the test, you can safely reuse it an unlimited number of times, which can save a lot of time and trouble.
Building static libraries is so easy that we have few problems with it. I don't want to show you how to build a static library. I'm only going to talk about Shared libraries here, because it's more complicated for most people.

Before we begin, let's outline what happens from the source code to the runtime:

Preprocessing: this stage processes all preprocessing instructions. It's basically all the lines in the source code that start with #, like #define and #include.
Compilation: once the source file has been preprocessed, the next step is compilation. Because many people refer to the entire program build process when they refer to compile time, this step is also referred to as "compilation proper." This step converts the.c file to the.o file.
Connection: at this point you should concatenate all your object files and libraries into the final runnable program. It's important to note that static libraries are actually embedded in your program, while Shared libraries simply contain references to them in your program. Now you have a complete program ready to run. When you start it from the shell, it is passed to the loader.
Load: this step occurs when your program starts. First the program needs to be scanned to reference the Shared library. All discovered references in the program take effect immediately, and the corresponding libraries are mapped to the program.
 

Steps 3 and 4 are the secret of Shared libraries.

Now, let's start with a simple example.

Foo. H:


 #ifndef foo_h__
#define foo_h__
extern void foo(void);
#endif  // foo_h__


Foo. C:

#include <stdio.h>

void foo(void)
{
    puts("Hello, I'm a shared library");
}

The main. C:


#include <stdio.h>
#include "foo.h"

int main(void)
{
    puts("This is a shared library test...");
    foo();
    return 0;
}
 

Oo. H defines an interface to our library, a simple function, foo(). Foo.c contains the implementation of this function, and main.c is a driver that USES our library.

To better illustrate this example, all the code is in the /home/username/foo directory.

Step 1: compile unconstrained bit code

We need to compile the source files of our library into unconstrained bit code. Unconstrained bit code is machine code stored in main memory and executed regardless of the absolute address.


$ gcc -c -Wall -Werror -fpic foo.c
 

Step 2: create a Shared library from an object file

Now let's turn the object file into a Shared library. Let's call it libfoo.so:


$ gcc -shared -o libfoo.so foo.o

Step 3: connect to Shared libraries

As you can see, everything is simple. We now have a Shared library. Now let's compile our main.c and connect it to libfoo. We'll call the final run program test. Note: the -lfoo option does not search for foo.o, but for libfoo.so. The GCC compiler assumes that all libraries begin with lib and end with.so or.a (.so means Shared object or Shared libraries,.a means archive archive, or static connection library).


$ gcc -Wall -o test main.c -lfoo
/usr/bin/ld: cannot find -lfoo
collect2: ld returned 1 exit status

Tell GCC where to find the Shared library

Uh - oh! The connector doesn't know where to find libfoo. GCC has a default search list, but our directory is not in that list. We need to tell GCC where to find libfoo. so. And that's where the -l option comes in. In this example, we will use the current directory /home/username/foo:


$ gcc -L/home/username/foo -Wall -o test main.c -lfoo

Step 4: use the library at runtime

Okay, no exceptions. Let's run the program:


$ ./test
./test: error while loading shared libraries: libfoo.so: cannot open shared object file: No such file or directory

Oh no! The loader could not find the Shared library. We didn't install it in a standard location, so we needed a helping hand loader. We have two options: use the environment variable LD_LIBRARY_PATH or rpath. Let's start with LD_LIBRARY_PATH:

Use LD_LIBRARY_PATH


$ echo $LD_LIBRARY_PATH
 

Nothing at the moment. Now add our working directory to LD_LIBRARY_PATH:


$ LD_LIBRARY_PATH=/home/username/foo:$LD_LIBRARY_PATH
$ ./test
./test: error while loading shared libraries: libfoo.so: cannot open shared object file: No such file or directory

Why are you still reporting an error? Although our directory is in LD_LIBRARY_PATH, we haven't exported it yet. In Linux, if you don't export your changes to an environment variable, they are not inherited by the quilt process. The loader and our test program did not inherit the changes we made, but rest assured that fixing the problem is simple:


$ export LD_LIBRARY_PATH=/home/username/foo:$LD_LIBRARY_PATH
$ ./test
This is a shared library test...
Hello, I'm a shared library

Good, it's working! LD_LIBRARY_PATH is great for quick tests, especially on systems where you don't have administrator privileges. On the other hand, exporting the LD_LIBRARY_PATH variable means that other programs that rely on LD_LIBRARY_PATH might have problems, so it's best to restore LD_LIBRARY_PATH to its previous form after you've done your testing.

Using rpath

Now let's try rpath. First, we need to clear LD_LIBRARY_PATH to make sure we're using rpath to search for library files. Rpath, or run path, is a way to embed Shared library locations in a program without relying on default locations and environment variables. We use rpath in connection. Note the "-wl,-rpath=/home/username/foo" option. -wl sends a comma-separated option to the connector, so we send the -rpath option to the connector through it. The comma delimiter is not followed by a space, but by the option to be sent. In this case, -rpath. Be sure to note that there is no space between "-wl,-rpath".


$ unset LD_LIBRARY_PATH
$ gcc -L/home/username/foo -Wl,-rpath=/home/username/foo -Wall -o test main.c -lfoo
$ ./test
This is a shared library test...
Hello, I'm a shared library

Very good. It worked. The rpath method is great because each program can list its own Shared library locations separately, so different programs no longer search for LD_LIBRARY_PATH on the wrong path.

Rpath and LD_LIBRARY_PATH

Rpath also has some negative sides. First, it requires that Shared libraries be installed in a fixed location so that all users can access the libraries in the same location. This means that there is not enough flexibility in the system configuration. Second, if the library involves NFS mounts or other network drivers, you may experience delays or worse when starting the program.

Modify ld.so using ldconfig

What if we want to make my library available to all users on the system? For this, you need administrator privileges. There are two reasons: first, put the library in a standard location, probably /usr/lib or /usr/local/lib, where the average user has no write permission. Second, you need to modify the ld.so profile and cache it. Do this as root:


$ cp /home/username/foo/libfoo.so /usr/lib
$ chmod 0755 /usr/lib/libfoo.so

The file is now in a standard location and is readable by all. We now need to tell the loader that the library files are available, so let's update the cache:


$ ldconfig

This will create a link to our Shared library and update the cache so that it takes effect immediately. Let's check again:


$ ldconfig -p | grep foo
libfoo.so (libc6) => /usr/lib/libfoo.so

Now that our library is installed, before we start testing it, we must clean up some other things:

Just in case, clean up LD_LIBRARY_PATH:


$ unset LD_LIBRARY_PATH

Reconnect our executable. Note: we do not need the -l option, because our library is saved in the default location, we can not use the rpath option:


$ gcc -Wall -o test main.c -lfoo
 

Let's make sure we use an instance of our library in /usr/lib, using the LDD command:


$ ldd test | grep foo
libfoo.so => /usr/lib/libfoo.so (0x00a42000)
 

Good, now run the program:


$ ./test
This is a shared library test...
Hello, I'm a shared library
 

That's all. We talked about how to build a Shared library, how to connect, how to solve the most common Shared library load problems, and the pros and cons of various approaches.

The attached:

1. The difference between Shared Libraries and Static Libraries

The Shared library is a file suffixed with.so (Windows platform for.dll, OS X platform for.dylib). All the code related to the library is in this file, which the program references at runtime. A program that USES a Shared library will only reference the code it USES in the Shared library.

Static libraries are files with the.a (Windows platform as.lib) suffix. All the code associated with the library is in this file, and the static library is linked directly to the program at compile time. A program that USES a static library copies the code it wants to use from the static library into itself. (Windows also has a.lib file that references.dll files, but it's the same as the first case.)

Each library has its own merits.

Using Shared libraries can reduce the amount of duplicate code in a program and make it smaller. And it lets you replace Shared objects with objects that do the same thing, so you can increase performance without recompiling programs that use the library. But using Shared libraries increases the execution cost of functions by a small amount, as does the runtime load cost, because symbols in Shared libraries need to be associated with what they use. Shared libraries can be loaded into programs at run time, which is the most common implementation mechanism for binary plug-in systems.

Static libraries increase program size in general, but they also mean you don't have to carry a copy of the library you're using everywhere. Because the code is already associated at compile time, there is no additional overhead at run time.

2. GCC first searches the library file in /usr/local/lib, then in /usr/lib, and then searches the -l parameter to specify the path. The search order is the same as that given by the -l parameter.

3. The default GNU loader, ld.so, searches the library files in the following order:

First search the DT_RPATH region in the program, unless there is also a DT_RUNPATH region.
Next, search for LD_LIBRARY_PATH. If the program is setuid/setgid, this step is skipped for security reasons.
Search the DT_RUNPATH area, unless the program is setuid/setgid.
Search the cache file /etc/ld-so-cache (to disable this step use the '-z nodeflib' loader parameter)
Search for the default directory /lib, then /usr/lib (to disable this step use the '-z nodeflib' loader parameter)


Related articles: