diff --git a/README.md b/README.md index 3c78a57..490a241 100644 --- a/README.md +++ b/README.md @@ -8,12 +8,13 @@ C and C++ minimal examples. Asserts used wherever possible. Hello worlds for coo 1. [Getting started](getting-started.md) 1. Featured - 1. [C](c/) - 1. [C++](cpp/) - 1. [POSIX](posix/): POSIX C API - 1. [OpenGL](opengl/) - 1. [GCC](gcc/): GCC extensions - 1. [KDE](kde/) + 1. [C](c/) + 1. [C++](cpp/) + 1. [POSIX](posix/): POSIX C API + 1. [OpenGL](opengl/) + 1. [GCC](gcc/): GCC extensions + 1. [Shared library](shared-library/) + 1. [KDE](kde/) 1. Introduction 1. [C vs C++](c-vs-cpp.md) 1. [Style guides](style-guides.md) diff --git a/shared-library/Makefile b/shared-library/Makefile new file mode 100644 index 0000000..a7be1bd --- /dev/null +++ b/shared-library/Makefile @@ -0,0 +1,56 @@ +CC := gcc -pedantic-errors -std=c89 -Wall + +.PHONY: all clean run + +all: maina.out mainso.out mainso_fullpath.out libab.so + +run: maina.out mainso.out mainso_fullpath.out + ./maina.out + ./mainso.out + ./mainso_fullpath.out + +install: + #sudo mv libabso /some/where/in/ld/path/ + #load path can be found with: + #ldconfig -v 2>/dev/null | grep -v $'^\t' + #sudo ldconfig + +# Main to link to .so +# +# readout -d shows that the ouptut stores the relative path +# +mainso.out: main.o libab.so + @# Will look for lib with basename *exactly* `libab.so`, + @# `libab.so.1` will not do! + $(CC) -L'.' main.o -o mainso.out -lab + @# With ':' uses full basename. + @# Application: select an specific version such as `libab.so.1` + @#$(CC) -L"." main.o -o mainso.out -l:libab.so + @#env LIBRARY_PATH=$LIBRARY_PATH:. $(CC) main.c -o mainso.out -lab + +# This is not recommended. +# +# Better use linker path as in mainso.out. +# +# readout -d shows that the ouptut stores the full path. +# +mainso_fullpath.out: main.o libab.so + $(CC) main.o '$(shell printf "`pwd`/libab.so")' -o mainso_fullpath.out + @# Does not work + @#$(CC) main.o -o mainso_fullpath.out -l"$(shell realpath libab.so)" + +maina.out: main.o ab.a + $(CC) main.o ab.a -o maina.out + +libab.so: a.o b.o + $(CC) -shared a.o b.o -o libab.so + @#$(CC) -shared -Wl,-soname,libab.so a.o b.o -o libab.so + +ab.a: a.o b.o + ar rcs ab.a a.o b.o + +%.o: %.c + $(CC) -fPIC -c '$<' -o '$@' + +clean: + rm -rf *.o *.a *.so *.out diff --git a/shared-library/README.md b/shared-library/README.md new file mode 100644 index 0000000..f1f768f --- /dev/null +++ b/shared-library/README.md @@ -0,0 +1,336 @@ +# Library + +Minimal example of a dynamic and static libraries. + +Tested on Ubuntu 14.04. + +## To use a compiled library you need + +- the compiled `.so` or `.a` + +- for C and C++, the header `.h` file(s). + + This allows your compiler to check at compile time that your are calling functions correctly (supposing of course that the `.h` files you include correspond to the actual library files) + + This is not needed in languages such as Fortran. Down point for Fortran. + +Either those must be in you compiler find path (different for headers and compiled files) or you must explicitly add them to the path. + +## Dynamic vs static + +Dynamic libraries are compiled libraries kept outside of the executable and are used at run time. + +They have `.so` extension on Linux and `.dll` on windows + +Dynamic libraries are different from static libraries (`.o` and `.a`) static libraries are put inside the executable at compile time. + +Advantages and disadvantages of dynamic libraries over static libraries are the usual trade-offs of share vs embed design choices. + +Advantages: + +- memory saving by keeping only a single version of the library + + Static libraries mean one version per executable + + This makes it absolutely essential to use dynamic libraries for very large libraries. + +- if the library inner working get updated to be (faster, use less memory), + + But not the interface ( inputs, outputs, side effects) + + There is no need to recompile the executable to get the updates. + +Disadvantages: + +- more complicated to use +- usage is OS dependant +- slight load overhead + +Since the disadvantages are so minor, it is almost always better to use dynamic linking. + +### Dynamic linking vs Dynamic loading + + + +## Search path + +Find where GCC search path for both `.a` and `.so`: + + gcc -print-search-dirs | grep '^libraries' | tr ':' $'\n' + +### -L option + +Append to search path of executable: + + gcc a.c -o a.out -L/full/path/to/ -lm + gcc a.c -o a.out -L./rel/path/to/ -lm + +### LIBRARY_PATH + +Colon separated list of paths that does the same as `-L`. + +## Static + +Gets included inside the generated executable, making it larger. + +You don't have to worry about dependencies. + + gcc -c a.c + gcc -c b.c + ar rcs a.a a.o b.o + gcc a.a c.c + +## Dynamic + +### loading vs linking + +There are two methods of using dynamic libraries in Linux: linking and loading. + +#### linking + +Link to lib for entire program. + +Simpler. + +#### loading + +Explicitly load needed functions during program execution. + +### create so + +*Must* compile like this: + + gcc -c -fPIC a.c + gcc -c -fPIC b.c + gcc -shared a.o b.o -o libab.so + +using `-fPIC` and `-shared`. + +### Version numbering + +Standard: up to 3 numbers. + +Yes, they come after the `.so` otherwise there would be ambiguity: `liba.1.so` is version 1 of `liba` or simply a library called `lib.a.1`? + +To link to a given version use full basename linking with version number. + +Linking takes care of version defaults: + +- `liba.so.1.1.1` + + Necessarily itself. + +- `liba.so.1.1` + + - Itself + - or a link to `1.1.1` + - or a link to `1.1.2` + - or a link to ... + +- `liba.so.1` + + - Itself, + - or a link to `1.1` + - or a link to `1.2` + - or a link to `1.1.2` + - or a link to `1.2.1` + - or a link to ... + +- `liba.so` + + - Itself, + - or a link to `1` + - or a link to `2` + - or a link to `1.1` + - or a link to `1.2` + - or a link to ... + +Rationale: if you underspecify the library you get by default the most recent. + +Convention: change in first number means possible interface break. + +TODO confirm: GCC does not resolve the conventional versioning names automatically: for library `a` to be found, there must be a file named exactly `liba.so` in the path. `liba.so.1` and others will not work. + +### Compile executable that depends on an so + +You must either: + +- tell `gcc` which libraries to use with the `-l` flag. This is usually the best method, as it only stores the basename of the shared library on the executable. +- pass the full path to the library to GCC. This is bad because it hard-codes the full path on the executable, making it less portable. + +The linker will check that the library is there and that it contains the necessary definitions. + +Also, the path information will be kept inside the executable. + +How this information is represented is a part of the `.elf` format definition. + +*Remember*: when the program will run, it must be able to find that `.so` again on the load path! + +#### l option + +#### What can be passed to -l + +The name given to `-l` must be either: + +- stripped from `lib` and `.so` part + + Ex: `m`, for `libm.so`. *will not work for `libm.so.1` !!!!! + +- colon `:` + full basename. Ex: `-l:libm.so.1` + +You need to compile like this so GCC can tell if all your functions are defined. + +#### Relative vs absolute + +The path to the so gets stored inside the elf so that it can be found when the program will load. + +Link to library libm.so: + + gcc a.c -o a.out -lm + gcc a.c -o a.out -l:libm.so + +Relative paths to the load path get stored in the elf file. + +`readelf -d` shows that: + + readelf -d a.out + +Store the full path in the elf file: + + gcc a.c -o a.out -l:/full/path/to/libm.so + + readelf -d a.out + +It must be in the load path. + +#### Append path to so search path + +##### LD_LIBRARY_PATH + + env LIBRARY_PATH=$LIBRARY_PATH:/path/to/ gcc a.c -o a.out -llib + +`LIBRARY_PATH` is different from `LD_LIBRARY_PATH`! `LIBRARY_PATH` is only used at compile time while `LD_LIBRARY_PATH` is only used at load time. + +A leading or trailing colon `:` implies that the current directory is included! + + :/some/path + /some/path: + +### Use so at runtime + +After an executable has been compiled to use an so, the so must be found at runtime. + +This is done by a program called the interpreter. + +The interpreter will use the library path stored inside the elf file that is being executed and will also search inside a search path called load path. + +There is no need to use the load path if an absolute path was stored in the executable, but this is not recommended since it would not be portable. + +#### Best production method + + sudo mv liba.so /some/where/in/link/path + sudo ldconfig + ./a.elf + +This supposes that when you compiled you used: `-lliba.so`. + +#### Environment variable + +Good: + + env LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/absolute/path/to/lib ./a.out + ./a.elf + +Bad: + + env LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./rel/path/to/lib/from/cd ./a.out + ./a.out + +This only works if you are in the right dir since relative path is take to current dir. + +`LD_LIBRARY_PATH` has nothing to do with `LIBRARY_PATH` path variable which is used during compilation by gcc! `LD_LIBRARY_PATH` is used during execution by the linker! + +#### Load path + +View library load path: + + cat /etc/ld.so.conf + +Remember: after modifying this file, you must update the load file cache or your changes will not be taken into effect. + +May also include other files by adding a line to that file: + + include /etc/ld.so.conf.d/*.conf + +This is done by default on Ubuntu. + +To take includes into consideration and print the actual search path, use `ldconfig`. + +So you also need to look inside included files for the libraries: + + cat /etc/ld.so.conf.d/*.conf + +The following paths are hard codded in `ldconfig`: + +- `/lib/` +- `/usr/lib/` + +#### View load path + +Print actual search path after resolving directives like `include`: + + ldconfig -v 2>/dev/null | grep -v $'^\t' + +Show directories that are scanned and libraries that are found in each dir: + + ldconfig -v + +Print cache stored in `/etc/ld.so.cache` and `.d` includes. does not show in which directory libraries are stored in, only where they link to: + + ldconfig -p + +##### hwcap + +When using commands like `ldconfig -v`, you may see outputs like: + + /usr/lib/i386-linux-gnu/sse2: (hwcap: 0x0000000004000000) + +`hwcap` stands for `hardware capacities` + +If present, means that those libraries can only be used if you hardware has the given capacities. + +Here for example, as shown in the directory name, this path is for libraries which depend on the sse2 extensions (a set of cpu instructions, not present in older CPUs). + +What the flags mean is defined by x86 and somewhat standardized across vendors: + + + +TODO where `ldconfig` finds this info: + +#### Cache + +It would be very slow to search the path every time. + +Therefore the linker keeps uses a cache at: + + cat /etc/ld.so.cache + +It first looks for libs there, and only then searches the path. + +You can generate `/etc/ld.so.cache` automatically once you have your `ld.so.conf` with `ldconfig`. + +Even if the linker finds the lib in the path, it does not automatically add it to the cache so you still have to run `ldconfig`. + +Running `ldconfig` is a part of every package install/uninstall if it contains a library. + +### Override symbols in libraries + +Symbols in `a.o` will override symbols in linked libs. + + echo "/path/to/my/a.o" | sudo tee -a /etc/ld.so.preload + +Useful mainly for emergency or tests. + +Can also be achieved via: + + export LD_PRELOAD= diff --git a/shared-library/a.c b/shared-library/a.c new file mode 100644 index 0000000..ca78fc6 --- /dev/null +++ b/shared-library/a.c @@ -0,0 +1,12 @@ +#include + +/* +Not mandatory in this example since we define the function here. + +But still a good idea to ensure that the prototypes are compatible. + +Often required because of header struct declarations. +*/ +#include "a.h" + +void a() { puts("a"); } diff --git a/shared-library/a.h b/shared-library/a.h new file mode 100644 index 0000000..733fc06 --- /dev/null +++ b/shared-library/a.h @@ -0,0 +1,6 @@ +#ifndef A_H +#define A_H + +void a(void); + +#endif diff --git a/shared-library/b.c b/shared-library/b.c new file mode 100644 index 0000000..ed93d6d --- /dev/null +++ b/shared-library/b.c @@ -0,0 +1,5 @@ +#include + +#include "b.h" + +void b() { puts("b"); } diff --git a/shared-library/b.h b/shared-library/b.h new file mode 100644 index 0000000..9bc5877 --- /dev/null +++ b/shared-library/b.h @@ -0,0 +1,6 @@ +#ifndef B_H +#define B_H + +void b(void); + +#endif diff --git a/shared-library/main.c b/shared-library/main.c new file mode 100644 index 0000000..2fb7eb7 --- /dev/null +++ b/shared-library/main.c @@ -0,0 +1,11 @@ +#include +#include + +#include "a.h" +#include "b.h" + +int main() { + a(); + b(); + return EXIT_SUCCESS; +}