Static, Shared Dynamic and Loadable Linux Libraries

Static, Shared Dynamic and Loadable Linux Libraries

http://www.yolinux.com/TUTORIALS/LibraryArchives-StaticAndDynamic.html

This tutorial discusses the philosophy behind libraries and the creation and use of C/C++ library shared components and plug-ins. The various technologies and methodologies used and insight to their appropriate application, is also discussed. In this tutorial, all libraries are created using the GNU Linux compiler.
本教程讨论库背后的哲学以及 C/C++ 库共享组件插件的创建和使用。还讨论了所使用的各种技术和方法以及对其适当应用的见解。在本教程中,所有库都是使用 GNU Linux 编译器创建的。

philosophy [fɪ'lɒsəfɪ]:n. 哲学哲理,人生观
insight ['ɪnsaɪt]:n. 洞察力,洞悉

1. Why libraries are used?

Libraries employ a software design also known as shared components or archive libraries, which groups together multiple compiled object code files into a single file known as a library. Typically C functions/C++ classes and methods which can be shared by more than one application are broken out of the application’s source code, compiled and bundled into a library. The C standard libraries and C++ STL are examples of shared components which can be linked with your code. The benefit is that each and every object file need not be stated when linking because the developer can reference the library collective. This simplifies the multiple use and sharing of software components between applications. It also allows application vendors a way to simply release an API to interface with an application. Components which are large can be created for dynamic use, thus the library can remain separate from the executable reducing it’s size and thus less disk space is used for the application. The library components are then called by various applications for use when needed.
库采用的软件设计也称为共享组件归档库,它将多个编译的目标代码文件组合在一起,形成一个称为库的文件。通常,可以由多个应用程序共享的 C functions/C++ classes and methods 从应用程序的源代码中分离出来,编译并捆绑到库中。C 标准库和 C++ STL 是可以与代码链接的共享组件的示例。好处是链接时不需要声明每个目标文件,因为开发人员可以引用库集合。这简化了应用程序之间软件组件的多次使用和共享。它还允许应用程序供应商简单地发布 API 以与应用程序进行交互。可以创建大型组件以供动态使用,因此库可以与可执行文件分开,从而减小其大小,从而减少了应用程序使用的磁盘空间。然后,各种应用程序调用库组件,以便在需要时使用。

collective [kə'lektɪv]:adj. 集体的,共同的,集合的,集体主义的 n. 集团,集合体,集合名词

Benefits include:

  • Component reuse: update one library, shared resource takes up less disk space.
    组件重用:更新一个库,共享资源占用更少的磁盘空间。

  • Version management: Linux libraries can cohabitate old and new versions on a single system.
    版本管理:Linux 库可以在单个系统上共存新旧版本。

  • Component Specialization: niche and specialized developers can focus on their core competency on a single library. Simplifies testing and verification.
    组件专业化:专业开发人员可以专注于他们在单个库上的核心竞争力。简化测试和验证。

niche [niːʃ; nɪtʃ]:n. 壁龛,合适的职业,有利可图的市场 (或形势等) vt. 放入壁龛

2. Linux Library Types

There are two Linux C/C++ library types which can be created:

  1. Static libraries (.a): Library of object code which is linked with, and becomes part of the application. (目标代码库,它与应用程序链接并成为应用程序的一部分。)
  2. Dynamically linked shared object libraries (.so): There is only one form of this library but it can be used in two ways. (这个库只有一种形式,但它可以以两种方式使用。)
    1. Dynamically linked at run time. The libraries must be available during compile/link phase. The shared objects are not included into the executable component but are tied to the execution. (在运行时动态链接。在编译/链接阶段,库必须可用。共享对象不包含在可执行组件中,但与执行相关联。)
    2. Dynamically loaded/unloaded and linked during execution (i.e. browser plug-in) using the dynamic linking loader system functions. (使用动态链接加载器系统功能在执行期间动态加载/卸载和链接 (i.e. browser plug-in)。)

Library naming conventions (库命名约定):
Libraries are typically named with the prefix lib. This is true for all the C standard libraries. When linking, the command line reference to the library will not contain the library prefix or suffix.
库通常以前缀 lib 命名。对于所有 C 标准库都是如此。链接时,对库的命令行引用不包含库前缀或后缀。

Consider the following compile and link command: gcc src-file.c -lm -lpthread
The libraries referenced in this example for inclusion during linking are the math library (m) and the thread library (pthread). They are found in /usr/lib/libm.a and /usr/lib/libpthread.a.
本示例中引用的用于在链接期间包含的库是数学库 (m) 和线程库 (pthread)。它们位于 /usr/lib/libm.a/usr/lib/libpthread.a 中。

Note: The GNU compiler now has the command line option -pthread while older versions of the compiler specify the pthread library explicitly with -lpthread. Thus now you are more likely to see gcc src-file.c -lm -pthread
注意:GNU 编译器现在具有命令行选项 -pthread,而旧版本的编译器使用 -lpthread 显式指定 pthread 库。因此,现在您更有可能看到 gcc src-file.c -lm -pthread

3. Static Libraries: (.a)

How to generate a static library (object code archive file):
如何生成静态库 (目标代码归档文件):

  • Compile: cc -Wall -c ctest1.c ctest2.c
    Compiler options:

    • -Wall: include warnings. See man page for warnings specified.
  • Create library libctest.a: ar -cvq libctest.a ctest1.o ctest2.o

  • List files in library: ar -t libctest.a

  • Linking with the library:
    cc -o executable-name prog.c libctest.a
    cc -o executable-name prog.c -L/path/to/library-directory -lctest

  • Example files:

ctest1.c

void ctest1(int *i)
{
   *i=5;
}

ctest2.c

void ctest2(int *i)
{
   *i=100;
}

prog.c

#include <stdio.h>
void ctest1(int *);
void ctest2(int *);

int main()
{
   int x;
   ctest1(&x);
   printf("Valx=%d\n",x);

   return 0;
}

Historical note: After creating the library it was once necessary to run the command: ranlib ctest.a. This created a symbol table within the archive. Ranlib is now embedded into the ar command.
历史记录:创建库后,曾经有必要运行命令:ranlib ctest.a。这在存档中创建了一个符号表。Ranlib 现在嵌入到 ar 命令中。

Note for MS/Windows developers: The Linux/Unix .a library is conceptually the same as the Visual C++ static .lib libraries.
Linux/Unix .a library 在概念上与 Visual C++ static .lib libraries。

conceptually [kən'sɛptʃʊəli]:adv. 概念上

4. Dynamically Linked Shared Object Libraries: (.so)

dynamically [daɪ'næmɪkli]:adv. 动态地,充满活力地,不断变化地

How to generate a shared object: (Dynamically linked object library file.) Note that this is a two step process.
如何生成共享对象:(动态链接对象库文件。) 请注意,这是一个两步的过程。

  1. Create object code
  2. Create library
  3. Optional: create default version using a symbolic link.

Library creation example

    gcc -Wall -fPIC -c *.c
    gcc -shared -Wl,-soname,libctest.so.1 -o libctest.so.1.0 *.o
    mv libctest.so.1.0 /opt/lib
    ln -sf /opt/lib/libctest.so.1.0 /opt/lib/libctest.so.1
    ln -sf /opt/lib/libctest.so.1.0 /opt/lib/libctest.so

This creates the library libctest.so.1.0 and symbolic links to it.
这将创建库 libctest.so.1.0 和它的符号链接。

It is also valid to cascade the linkage:
级联链接也是有效的:

    ln -sf /opt/lib/libctest.so.1.0 /opt/lib/libctest.so.1
    ln -sf /opt/lib/libctest.so.1 /opt/lib/libctest.so
cascade [kæs'keɪd]:n. 小瀑布,瀑布状物,串联 vi. 像瀑布般大量倾泻下来 vi. 像瀑布般悬挂着
consistent [kən'sɪst(ə)nt]:adj. 始终如一的,一致的,坚持的

If you look at the libraries in /lib/ and /usr/lib/ you will find both methodologies present. Linux developers are not consistent. What is important is that the symbolic links eventually point to an actual library.
如果查看 /lib/ and /usr/lib/ 中的库,您会发现两种方法都存在。Linux 开发人员不一致。重要的是符号链接最终指向实际的库。

Compiler options:

  • -Wall: include warnings. See man page for warnings specified. (包括警告。有关指定的警告,请参见手册页。)
  • -fPIC: Compiler directive to output position independent code, a characteristic required by shared libraries. Also see -fpic. (编译器指令输出位置无关代码,共享库所需的特性。另见 -fpic。)
  • -shared: Produce a shared object which can then be linked with other objects to form an executable. (生成一个共享对象,然后可以将其与其他对象链接以形成可执行文件。)
  • -Wl,options: Pass options to linker. (将选项传递给链接器。)
    In this example the options to be passed on to the linker are: -soname libctest.so.1. The name passed with the -o option is passed to gcc. (在此示例中,要传递给链接器的选项是:-soname libctest.so.1。使用 -o 选项传递的名称将传递给 gcc。)
  • Option -o: Output of operation. In this case the name of the shared object to be output will be libctest.so.1.0 (输出操作。在这种情况下,要输出的共享对象的名称将是 libctest.so.1.0。)

Library Links:

  • The link to /opt/lib/libctest.so allows the naming convention for the compile flag -lctest to work. (指向 /opt/lib/libctest.so 的链接允许编译标志 -lctest 的命名约定起作用。)
  • The link to /opt/lib/libctest.so.1 allows the run time binding to work. See dependency below. (指向 /opt/lib/libctest.so.1 的链接允许运行时绑定工作。见下面的依赖。)

Compile main program and link with shared object library:
Compiling for run-time linking with a dynamically linked libctest.so.1.0:
使用动态链接的 libctest.so.1.0 编译运行时链接:

    gcc -Wall -I/path/to/include-files -L/path/to/libraries prog.c -lctest -o prog

Use:

    gcc -Wall -L/opt/lib prog.c -lctest -o prog

Where the name of the library is libctest.so. (This is why you must create the symbolic links or you will get the error /usr/bin/ld: cannot find -lctest.)
库的名称是 libctest.so。(这就是你必须创建符号链接的原因,否则你会收到错误 /usr/bin/ld: cannot find -lctest。)

The libraries will NOT be included in the executable but will be dynamically linked during run-time execution.
这些库不会包含在可执行文件中,但会在运行时执行期间动态链接。

List Dependencies
The shared library dependencies of the executable can be listed with the command: ldd name-of-executable
可执行文件的共享库依赖项可以使用以下命令列出:ldd name-of-executable

Example: ldd prog

        libctest.so.1 => /opt/lib/libctest.so.1 (0x00002aaaaaaac000)
        libc.so.6 => /lib64/tls/libc.so.6 (0x0000003aa4e00000)
        /lib64/ld-linux-x86-64.so.2 (0x0000003aa4c00000)

[Potential Pitfall]: Unresolved errors within a shared library may cause an error when the library is loaded. Example:
加载库时,共享库中未解析的错误可能会导致错误。

Error message at run-time:

        ERROR: unable to load libname-of-lib.so
        ERROR: unable to get function address

Investigate error:

    [prompt]$ ldd libname-of-lib.so
        libglut.so.3 => /usr/lib64/libglut.so.3 (0x00007fb582b74000)
        libGL.so.1 => /usr/lib64/libGL.so.1 (0x00007fb582857000)
        libX11.so.6 => /usr/lib64/libX11.so.6 (0x00007fb582518000)
        libIL.so.1 (0x00007fa0f2c0f000)
        libcudart.so.4 => not found
potential [pəˈtenʃl]:adj. 潜在的,可能的,势的 n. 潜能,可能性,电势
pitfall ['pɪtfɔːl]:n. 陷阱,圈套,缺陷,诱惑
problematic [prɒblə'mætɪk]:adj. 问题的,有疑问的,不确定的

The first three libraries show that there is a path resolution. The last two are problematic.
前三个库存在路径解析。最后两个是有问题的。

The fix is to resolve dependencies of the last two libraries when linking the library libname-of-lib.so:
在链接库 libname-of-lib.so 时解决最后两个库的依赖关系

  • Add the unresolved library path in /etc/ld.so.conf.d/name-of-lib-x86_64.conf and/or /etc/ld.so.conf.d/name-of-lib-i686.conf (在 /etc/ld.so.conf.d/name-of-lib-x86_64.conf and/or /etc/ld.so.conf.d/name-of-lib-i686.conf 中添加未解析的库路径)
    Reload the library cache (/etc/ld.so.cache) with the command: sudo ldconfig (使用以下命令重新加载库高速缓存 (/etc/ld.so.cache):sudo ldconfig)
    or
  • Add library and path explicitly to the compiler/linker command: -lname-of-lib -L/path/to/lib (将库和路径显式添加到编译器/链接器命令:-lname-of-lib -L/path/to/lib)
    or
  • Add the library path to the environment variable to fix run-time dependency: (将库路径添加到环境变量以修复运行时依赖性:)
    export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/path/to/lib

Run Program
Set path: export LD_LIBRARY_PATH=/opt/lib:$LD_LIBRARY_PATH
Run: prog

Example with code
Using the example code above for ctest1.c, ctest2.c and prog.c
1. Compile the library functions (编译库函数): gcc -Wall -fPIC -c ctest1.c ctest2.c
2. Generate the shared library (生成共享库): gcc -shared -Wl,-soname,libctest.so.1 -o libctest.so.1.0 ctest1.o ctest2.o
This generates the library libctest.so.1.0 (这将生成库 libctest.so.1.0)
3. Move to lib/ directory (移至 lib/ 目录):

sudo mv libctest.so.1.0 /opt/lib
sudo ln -sf /opt/lib/libctest.so.1.0 /opt/lib/libctest.so.1
sudo ln -sf /opt/lib/libctest.so.1 /opt/lib/libctest.so

Compile program for use with a shared library (用共享库的编译程序): gcc -Wall -L/opt/lib prog.c -lctest -o prog
[Potential Pitfall]: If the symbolic links are not created (above), you will get the following error ([潜在缺陷]:如果未创建符号链接 (上面),您将收到以下错误):

/usr/bin/ld: cannot find -lctest
collect2: error: ld returned 1 exit status

The reference to the library name -lctest refers to /opt/lib/libctest.so (对库名称 -lctest 的引用是指 /opt/lib/libctest.so)

4. Configure the library path (see below and choose one of three mechanisms). (配置库路径 (请参阅下文并选择三种机制之一))
In this example we set the environment variable (在此示例中,我们设置环境变量): export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/lib

5. Run the program: ./prog

Valx=5

[Potential Pitfall]: You get the following error if the library path is not set ([潜在缺陷]:如果未设置库路径,则会出现以下错误):

./prog: error while loading shared libraries: libctest.so.1: cannot open shared object file: No such file or directory

Man Pages
gcc - GNU C compiler
ld - The GNU Linker
ldd - List library dependencies (print shared object dependencies) - 列出库依赖项
ldconfig - configure dynamic linker run-time bindings (update cache /etc/ld.so.cache)

Links
LDP: Shared libraries
http://www.tldp.org/HOWTO/Program-Library-HOWTO/index.html

4.1 Library Path

In order for an executable to find the required libraries to link with during run time, one must configure the system so that the libraries can be found. Methods available: (Do at least one of the following)
为了使可执行文件能够在运行时找到要链接的所需库,必须配置系统以便可以找到库。可用方法:(至少执行以下一项操作)

1. Add library directories to be included during dynamic linking to the file /etc/ld.so.conf (添加库目录至动态链接期间包含的文件 /etc/ld.so.conf)
Sample: /etc/ld.so.conf

/usr/X11R6/lib
/usr/lib
...
..
/usr/lib/sane
/usr/lib/mysql
/opt/lib

Add the library path to this file and then execute the command (as root) ldconfig to configure the linker run-time bindings.
将库路径添加到此文件,然后执行命令 (以 root 身份) ldconfig 以配置链接器运行时绑定。

You can use the -f file-name flag to reference another configuration file if you are developing for different environments.
如果要针对不同的环境进行开发,可以使用 -f file-name 标志来引用另一个配置文件。

See man page for command ldconfig.
有关命令 ldconfig,请参见手册页。

OR

2. Add specified directory to library cache (将指定目录添加到库高速缓存): (as root)
ldconfig -n /opt/lib
Where /opt/lib is the directory containing your library libctest.so (其中 /opt/lib 是包含库 libctest.so 的目录)
(When developing and just adding your current directory: ldconfig -n . Link with -L.)
(开发时只需添加当前目录:ldconfig -n . Link with -L。)

This will NOT permanently configure the system to include this directory. The information will be lost upon system reboot.
这不会永久配置系统以包含此目录。系统重启后信息将丢失。

OR

permanently ['pɜːm(ə)nəntlɪ]:adv. 永久地,长期不变地

3. Specify the environment variable LD_LIBRARY_PATH to point to the directory paths containing the shared object library. This will specify to the run time loader that the library paths will be used during execution to resolve dependencies.
指定环境变量 LD_LIBRARY_PATH 以指向包含共享对象库的目录路径。这将指定运行时加载程序在执行期间将使用库路径来解析依赖关系。

(Linux/Solaris: LD_LIBRARY_PATH, SGI: LD_LIBRARYN32_PATH, AIX: LIBPATH, Mac OS X: DYLD_LIBRARY_PATH, HP-UX: SHLIB_PATH)
Example (bash shell): export LD_LIBRARY_PATH=/opt/lib:$LD_LIBRARY_PATH or add to your ~/.bashrc file:

...
if [ -d /opt/lib ];
then
   LD_LIBRARY_PATH=/opt/lib:$LD_LIBRARY_PATH
fi

...

export LD_LIBRARY_PATH

This instructs the run time loader to look in the path described by the environment variable LD_LIBRARY_PATH, to resolve shared libraries. This will include the path /opt/lib.
这指示运行时加载程序查看环境变量 LD_LIBRARY_PATH 描述的路径,以解析共享库。这将包括路径 /opt/lib

Library paths used should conform to the Linux Standard Base directory structure. (使用的库路径应符合 Linux Standard Base 目录结构。)
https://wiki.linuxfoundation.org/lsb/start

Linux Standard Base,LSB:

4.2 Library Info

ar: list object files in archive library
ar tf /usr/lib/x86_64-linux-gnu/libjpeg.a

This will list all of the object files held in the archive library (这将列出归档库中保存的所有目标文件):

jlibinit.o
jcapimin.o
jcapistd.o
jccoefct.o
jccolor.o
jcdctmgr.o
jchuff.o
jcinit.o
...
...

ar - create, modify, and extract from archives

4.3 nm: list symbols: object files, archive library and shared library

The command nm lists symbols contained in object files (命令 nm 列出了目标文件中包含的符号):

 nm file.o

The command nm lists symbols contained in the archive library (命令 nm 列出了归档库中包含的符号):

Object symbols in static archive libraries are categorized using the source and object file hierarchy of the library (静态归档库中的目标符号使用库的源和目标文件层次结构进行分类):

 nm /usr/lib/x86_64-linux-gnu/libjpeg.a
jlibinit.o:
0000000000000000 B auxv
                 U fclose
                 U fopen
                 U fread
                 U getpagesize
0000000000000000 T libjpeg_general_init
                 U malloc
                 U perror

jcapimin.o:
                 U jinit_marker_writer
                 U jinit_memory_mgr
0000000000000000 T jpeg_CreateCompress
                 U jpeg_abort
0000000000000240 T jpeg_abort_compress
                 U jpeg_destroy
0000000000000230 T jpeg_destroy_compress
00000000000002a0 T jpeg_finish_compress
                 U jpeg_natural_order
...
...

The command nm lists symbols contained in the object file or shared library.
命令 nm 列出目标文件或共享库中包含的符号。

Use the command nm -D libctest.so.1.0 (or nm --dynamic libctest.so.1.0)

0000000000100988 A __bss_start
000000000000068c T ctest1
00000000000006a0 T ctest2
                 w __cxa_finalize
00000000001007b0 A _DYNAMIC
0000000000100988 A _edata
0000000000100990 A _end
00000000000006f8 T _fini
0000000000100958 A _GLOBAL_OFFSET_TABLE_
                 w __gmon_start__
00000000000005b0 T _init
                 w _Jv_RegisterClasses

Note that other platforms (Cygwin) may not respond to -D. Try nm -gC libctest.so.1.0

nm - list symbols from object files

Symbol TypeDescription
AThe symbol’s value is absolute, and will not be changed by further linking. (符号的值是绝对值,不会通过进一步链接来更改。)
BUn-initialized data section
DInitialized data section
TNormal code section
UUndefined symbol used but not defined. Dependency on another library. (使用了未定义的符号但未定义。依赖于另一个库。)
WDoubly defined symbol. If found, allow definition in another library to resolve dependency. (双重定义的符号。如果找到,则允许在另一个库中定义以解决依赖性。)

objdump - display information from object files.

4.4 readelf: list symbols in shared library

The command readelf command to list symbols contained in a shared library (命令 readelf 用于列出共享库中包含的符号):

Use the command readelf -s /usr/lib64/libjpeg.so

Symbol table '.dynsym' contains 144 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000003b30     0 SECTION LOCAL  DEFAULT   10 
     2: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND getenv@GLIBC_2.2.5 (4)
     3: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND free@GLIBC_2.2.5 (4)
     4: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND ferror@GLIBC_2.2.5 (4)
     5: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND fread@GLIBC_2.2.5 (4)
     6: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND fclose@GLIBC_2.2.5 (4)
     7: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __stack_chk_fail@GLIBC_2.4 (5)
     8: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND memset@GLIBC_2.2.5 (4)
     9: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
...
...

readelf - Displays information about ELF files.

5. Library Versions

Library versions should be specified for shared objects if the function interfaces are expected to change (C++ public/protected class definitions), more or fewer functions are included in the library, the function prototype changes (return data type (int, const int, …) or argument list changes) or data type changes (object definitions: class data members, inheritance, virtual functions, …).
如果更改函数接口 (C++ public/protected class definitions),库中包含更多或更少的函数,函数原型更改 (return data type (int, const int, …) or argument list changes) 或数据类型更改 (object definitions: class data members, inheritance, virtual functions, …),则应为共享对象指定库版本。

The library version can be specified when the shared object library is created. If the library is expected to be updated, then a library version should be specified. This is especially important for shared object libraries which are dynamically linked. This also avoids the Microsoft “DLL hell” problem of conflicting libraries where a system upgrade which changes a standard library breaks an older application expecting an older version of the the shared object function.
可以在创建共享库时指定库版本。如果希望更新库,则应指定库版本。这对于动态链接的共享对象库尤其重要。这也避免了冲突库的 Microsoft DLL hell 问题,其中系统升级更改标准库,打破了期望旧版本的共享对象功能的旧应用程序。

hell [hel]:n. 地狱,究竟,训斥,黑暗势力 vi. 过放荡生活,飞驰 int. 该死,见鬼

Versioning occurs with the GNU C/C++ libraries as well. This often make binaries compiled with one version of the GNU tools incompatible with binaries compiled with other versions unless those versions also reside on the system. Multiple versions of the same library can reside on the same system due to versioning. The version of the library is included in the symbol name so the linker knows which version to link with.
GNU C/C++ 库也进行了版本控制。这通常会使用一个版本的 GNU 工具编译的二进制文件与使用其他版本编译的二进制文件不兼容,除非这些版本也驻留在系统上。由于版本控制,同一个库的多个版本可以驻留在同一系统上。库的版本包含在符号名称中,因此链接器知道要链接的版本。

One can look at the symbol version used: nm csub1.o

00000000 T ctest1

No version is specified in object code by default.
默认情况下,在目标代码中不指定版本。

ld and object file layout

There is one GNU C/C++ compiler flag that explicitly deals with symbol versioning. Specify the version script to use at compile time with the flag: --version-script=your-version-script-file
有一个 GNU C/C++ 编译器标志明确处理符号版本控制。使用以下标志指定要在编译时使用的版本脚本:--version-script=your-version-script-file

Note: This is only useful when creating shared libraries. It is assumed that the programmer knows which libraries to link with when static linking. Run-time linking allows opportunity for library incompatibility.
这仅在创建共享库时有用。假设程序员在静态链接时知道要链接哪些库。运行时链接允许库不兼容的机会。

opportunity [ˌɒpəˈtjuːnəti]:n. 时机,机会

GNU/Linux, see examples of version scripts here: sysdeps/unix/sysv/linux/Versions

Some symbols may also get version strings from assembler code which appears in glibc headers files. Look at include/libc-symbols.h.
某些符号也可能从汇编代码中获取版本字符串,该代码出现在 glibc 头文件中。

Example: nm /lib/libc.so.6 | more

00000000 A GCC_3.0
00000000 A GLIBC_2.0
00000000 A GLIBC_2.1
00000000 A GLIBC_2.1.1
00000000 A GLIBC_2.1.2
00000000 A GLIBC_2.1.3
00000000 A GLIBC_2.2
00000000 A GLIBC_2.2.1
00000000 A GLIBC_2.2.2
00000000 A GLIBC_2.2.3
00000000 A GLIBC_2.2.4
...

Note the use of a version script.
Library referencing a versioned library (库引用版本化库): nm /lib/libutil-2.2.5.so

..
...
         U strcpy@@GLIBC_2.0
         U strncmp@@GLIBC_2.0
         U strncpy@@GLIBC_2.0
...
..

Links: https://sourceware.org/binutils/docs/ld/index.html

6. Dynamic loading and un-loading of shared libraries using libdl

These libraries are dynamically loaded / unloaded and linked during execution. Useful for creating a plug-in architecture.
这些库在执行期间动态加载/卸载和链接。用于创建 plug-in 架构。

Prototype include file for the library: ctest.h

prototype ['prəʊtətaɪp]:n. 原型,标准,模范
#ifndef CTEST_H
#define CTEST_H

#ifdef __cplusplus
extern "C" {
#endif

void ctest1(int *);
void ctest2(int *);

#ifdef __cplusplus
}
#endif

#endif

Use the notation extern "C" so the libraries can be used with C and C++. This statement prevents the C++ from name mangling and thus creating unresolved symbols when linking.
使用符号 extern "C",这样库可以与 C 和 C++ 一起使用。此语句可防止 C++ 名称损坏,在链接时创建未解析的符号

mangle ['mæŋg(ə)l]:vt. 乱砍,轧布,损坏 n. 轧布机

Load and unload the library libctest.so (created above), dynamically (动态加载和卸载库 libctest.so (上面创建)):

#include <stdio.h>
#include <dlfcn.h>
#include "ctest.h"

int main(int argc, char **argv) 
{
   void *lib_handle;
   double (*fn)(int *);
   int x;
   char *error;

   lib_handle = dlopen("/opt/lib/libctest.so", RTLD_LAZY);
   if (!lib_handle) 
   {
      fprintf(stderr, "%s\n", dlerror());
      exit(1);
   }

   fn = dlsym(lib_handle, "ctest1");
   if ((error = dlerror()) != NULL)  
   {
      fprintf(stderr, "%s\n", error);
      exit(1);
   }

   (*fn)(&x);
   printf("Valx=%d\n",x);

   dlclose(lib_handle);
   return 0;
}
gcc -rdynamic -o progdl progdl.c -ldl

Explanation:

  • dlopen("/opt/lib/libctest.so", RTLD_LAZY);
    Open shared library named libctest.so. (打开名为 libctest.so 的共享库。)
    The second argument indicates the binding. See include file dlfcn.h. (第二个参数表示绑定。请参阅包含文件 dlfcn.h。)
    Returns NULL if it fails. (如果失败则返回 NULL。)
    Options:

    • RTLD_LAZY: If specified, Linux is not concerned about unresolved symbols until they are referenced. (如果指定,Linux 在引用之前不关心未解析的符号。)
    • RTLD_NOW: All unresolved symbols resolved when dlopen() is called. (调用 dlopen() 时解析所有未解析的符号。)
    • RTLD_GLOBAL: Make symbol libraries visible. (使符号库可见。)
  • dlsym(lib_handle, "ctest1");
    Returns address to the function which has been loaded with the shared library. (返回已加载共享库的函数的地址。)
    Returns NULL if it fails. (如果失败则返回 NULL。)
    Note: When using C++ functions, first use nm to find the “mangled” symbol name or use the extern "C" construct to avoid name mangling. (使用 C++ 函数时,首先使用 nm 查找“受损”符号名称或使用 extern "C" 构造来避免名称错位。)
    i.e. extern "C" void function-name();

Object code location: Object code archive libraries can be located with either the executable or the loadable library. Object code routines used by both should not be duplicated in each. This is especially true for code which use static variables such as singleton classes. A static variable is global and thus can only be represented once. Including it twice will provide unexpected results. The programmer can specify that specific object code be linked with the executable by using linker commands which are passed on by the compiler.
Object code location:目标代码归档库可以与可执行文件库或可加载库一起定位。两者使用的目标代码例程不应在每个例程中重复。对于使用静态变量 (singleton classes) 的代码尤其如此。静态变量是全局的,因此只能表示一次。包括它两次将提供意想不到的结果。程序员可以通过使用编译器传递的链接器命令指定特定目标代码与可执行文件链接。

Use the -Wl gcc/g++ compiler flag to pass command line arguments on to the GNU ld linker.
使用 -Wl gcc/g++ 编译器标志将命令行参数传递给 GNU ld 链接器。

Example makefile statement: g++ -rdynamic -o appexe $(OBJ) $(LINKFLAGS) -Wl,--whole-archive -L{AA_libs} -laa -Wl,--no-whole-archive $(LIBS)

  • --whole-archive: This linker directive specifies that the libraries listed following this directive (in this case AA_libs) shall be included in the resulting output even though there may not be any calls requiring its presence. This option is used to specify libraries which the loadable libraries will require at run time.
    此链接器指令指定在此指令之后列出的库 (在本例中为 AA_libs) 应包含在结果输出中,即使可能没有任何调用需要它存在。此选项用于指定可加载库在运行时将需要的库。

  • -no-whole-archive: This needs to be specified whether you list additional object files or not. The gcc/g++ compiler will add its own list of archive libraries and you would not want all the object code in the archive library linked in if not needed. It toggles the behavior back to normal for the rest of the archive libraries.
    无论是否列出其他对象文件,都需要指定。gcc/g++ 编译器将添加自己的归档库列表,如果不需要,您不希望归档库中的所有目标代码都链接在一起。它会将其他存档库的行为切换回正常状态。

Man pages:
dlopen() - gain access to an executable object file (获得对可执行目标文件的访问权限)
dclose() - close a dlopen object (关闭一个 dlopen 对象)
dlsym() - obtain the address of a symbol from a dlopen object (从 dlopen 对象获取符号的地址)
dlvsym() - Programming interface to dynamic linking loader. (编程动态链接加载器的接口。)
dlerror() - get diagnostic information (获取诊断信息)

Links:
GNOME Glib dynamic loading of modules - cross platform API for dynamically loading “plug-ins”.
Dynamic Loading of Modules - portable method for dynamically loading plug-ins (用于动态加载 plug-ins 的便携方法)

7. C++ class objects and dynamic loading

7.1 C++ and name mangling

When running the above C examples with the C++ compiler one will quickly find that C++ function names get mangled and thus will not work unless the function definitions are protected with extern "C"{}.
当使用 C++ 编译器运行上述 C 示例时,很快就会发现 C++ 函数名称被破坏,因此除非函数定义受 extern "C"{} 保护,否则将无法工作。

mangle ['mæŋg(ə)l]:vt. 乱砍,轧布,损坏 n. 轧布机

Note that the following are not equivalent (请注意,以下内容不相同):

extern "C"
{
   int functionx();
}
extern "C" int functionx();

The following are equivalent (以下是等效的):

extern "C"
{
   extern int functionx();
}
extern "C" int functionx();

7.2 Dynamic loading of C++ classes

The dynamic library loading routines enable the programmer to load C functions. In C++ we would like to load class member functions. In fact the entire class may be in the library and we may want to load and have access to the entire object and all of its member functions. Do this by passing a C class factory function which instantiates the class.
动态库加载例程使程序员能够加载 C 函数。在 C++ 中,我们想加载类成员函数。事实上,整个类可能在库中,我们可能希望加载并访问整个对象及其所有成员函数。通过传递实例化类的 C 类工厂函数来完成此操作。

The class .h file:

class Abc {

...
...

};

// Class factory "C" functions

typedef Abc* create_t;
typedef void destroy_t(Abc*);

The class .cpp file:

Abc::Abc()
{
    ...
}

extern "C"
{
   // These two "C" functions manage the creation and destruction of the class Abc

   Abc* create()
   {
      return new Abc;
   }

   void destroy(Abc* p)
   {
      delete p;   // Can use a base class or derived class pointer here
   }
}

This file is the source to the library. The C functions to instantiate (create) and destroy a class defined in the dynamically loaded library where Abc is the C++ class.
该文件是库的源。C 函数实例化 (创建) 和销毁在动态加载的库中定义的类,其中 Abc 是 C++ 类。

Main executable which calls the loadable libraries (调用可加载库的主要可执行文件):

// load the symbols
    create_t* create_abc = (create_t*) dlsym(lib_handle, "create");

...
...

    destroy_t* destroy_abc = (destroy_t*) dlsym(lib_handle, "destroy");

...
...

Pitfalls:
The new/delete of the C++ class should both be provided by the executable or the library but not split. This is so that there is no surprise if one overloads new/delete in one or the other.
C++ 类的 new/delete 应该由可执行文件或库提供,但不能拆分。这样,如果一个人在一个或另一个中重载 new / delete 就不足为奇了。

Links:
Dynamic Class Loading for C++ on Linux https://www.linuxjournal.com/article/3687

pitfall ['pɪtfɔːl]:n. 陷阱,圈套,缺陷,诱惑

8. Comparison to the Microsoft DLL

The Microsoft Windows equivalent to the Linux / Unix shared object (.so) is the .dll. The Microsoft Windows DLL file usually has the extension .dll, but may also use the extension .ocx. On the old 16 bit windows, the dynamically linked libraries were also named with the .exe suffix. Executing the DLL will load it into memory.
相当于 Linux / Unix 共享对象 (.so) 的 Microsoft Windows 是 .dll。Microsoft Windows DLL 文件通常具有扩展名 .dll,但也可以使用扩展名 .ocx。在旧的 16 位窗口上,动态链接的库也以 .exe 后缀命名。执行 DLL 会将其加载到内存中。

The Visual C++ .NET IDE wizard will create a DLL framework through the GUI, and generates a .def file. This module definition file lists the functions to be exported. When exporting C++ functions, the C++ mangled names are used. Using the Visual C++ compiler to generate a .map file will allow you to discover the C++ mangled name to use in the .def file. The SECTIONS label in the .def file will define the portions which are shared. Unfortunately the generation of DLLs are tightly coupled to the Microsoft IDE, so much so that I would not recommend trying to create one without it.
Visual C++ .NET IDE 向导将通过 GUI 创建 DLL 框架,并生成 .def 文件。这个模块定义文件列出了要导出的函数。导出 C++ 函数时,使用 C++ 错位名称。使用 Visual C++ 编译器生成 .map 文件将允许您发现要在 .def 文件中使用的 C++ 受损名称。.def 文件中的 SECTIONS 标签将定义共享部分。不幸的是,DLL 的生成与 Microsoft IDE 紧密耦合,以至于我不建议尝试在没有它的情况下创建一个。

The Microsoft Windows C++ equivalent functions to libdl are the following functions (与 libdl 相同的 Microsoft Windows C++ 函数是以下函数):

  • ::LoadLibrary() - dlopen()
  • ::GetProcAddress() - dlsym()
  • ::FreeLibrary() - dlclose()

[Potential Pitfall]: Microsoft Visual C++ .NET compilers do not allow the linking control that the GNU linker ld allows (i.e. --whole-archive, -no-whole-archive). All symbols need to be resolved by the VC++ compiler for both the loadable library and the application executable individually and thus it can cause duplication of libraries when the library is loaded. This is especially bad when using static variables (i.e. used in singleton patterns) as you will get two memory locations for the static variable, one used by the loadable library and the other used by the program executable. This breaks the whole static variable concept and the singleton pattern. Thus you can not use a static variable which is referenced by by both the loadable library and the application executable as they will be unique and different. To use a unique static variable, you must pass a pointer to that static variable to the other module so that each module (main executable and DLL library) can use the same instantiation. On MS/Windows you can use shared memory or a memory mapped file so that the main executable and DLL library can share a pointer to an address they both will use.
Microsoft Visual C++ .NET 编译器不允许 GNU 链接器 ld 允许的链接控件 (i.e. --whole-archive, -no-whole-archive)。VC++ 编译器需要为可加载库和应用程序可执行文件单独解析所有符号,因此在加载库时可能导致库的重复。当使用静态变量 (即在单例模式中使用) 时,这尤其糟糕,因为您将获得静态变量的两个内存位置,一个由可加载库使用,另一个由程序可执行文件使用。这打破了整个静态变量概念和单例模式。因此,您不能使用可加载库和应用程序可执行文件引用的静态变量,因为它们将是唯一且不同的。要使用唯一的静态变量,必须将指向该静态变量的指针传递给另一个模块,以便每个模块 (主可执行文件和 DLL 库) 可以使用相同的实例。在 MS/Windows 上,您可以使用共享内存或内存映射文件,以便主可执行文件和 DLL 库可以共享指向它们将使用的地址的指针。

Cross platform (Linux and MS/Windows) C++ code snippet:

snippet ['snɪpɪt]:n. 小片,片断,不知天高地厚的年轻人

Include file declaration: (.h or .hpp)

class Abc{
public:
   static Abc* Instance(); // Function declaration. Could also be used as a public class member function.

private:
   static Abc *mInstance;      // Singleton. Use this declaration in C++ class member variable declaration.
   ...
}

C/C++ Function source: (.cpp)

/// Singleton instantiation
Abc* Abc::mInstance = 0;   // Use this declaration for C++ class member variable
                           // (Defined outside of class definition in ".cpp" file)

// Return unique pointer to instance of Abc or create it if it does not exist.
// (Unique to both exe and dll)

static Abc* Abc::Instance() // Singleton
{
#ifdef WIN32
    // If pointer to instance of Abc exists (true) then return instance pointer else look for 
    // instance pointer in memory mapped pointer. If the instance pointer does not exist in
    // memory mapped pointer, return a newly created pointer to an instance of Abc.

    return mInstance ? 
       mInstance : (mInstance = (Abc*) MemoryMappedPointers::getPointer("Abc")) ? 
       mInstance : (mInstance = (Abc*) MemoryMappedPointers::createEntry("Abc",(void*)new Abc));
#else
    // If pointer to instance of Abc exists (true) then return instance pointer 
    // else return a newly created pointer to an instance of Abc.

    return mInstance ? mInstance : (mInstance = new Abc);
#endif
}

Windows linker will pull two instances of object, one in exe and one in loadable module. Specify one for both to use by using memory mapped pointer so both exe and loadable library point to same variable or object.
Windows 链接器将拉出两个对象实例,一个在 exe 中,一个在可加载模块中。通过使用内存映射指针为两者指定一个,以便 exe 和可加载库指向同一个变量或对象。

Note that the GNU linker does not have this problem.
请注意,GNU 链接器没有此问题。

For more on singletons see the YoLinux.com C++ singleton software design pattern tutorial.
http://www.yolinux.com/TUTORIALS/C++Singleton.html

singleton ['sɪŋg(ə)lt(ə)n]:n. 单独的人 (或物体),单生子女,单生幼畜,单身的人,(纸牌戏,尤指桥牌) 单张,单元 (素) 集

9. Cross platform programming of loadable libraries

#ifndef USE_PRECOMPILED_HEADERS
#ifdef WIN32
#include <direct.h>
#include <windows.h>
#else
#include <sys/types.h>
#include <dlfcn.h>
#endif
#include <iostream>
#endif

    using namespace std;

#ifdef WIN32
    HINSTANCE lib_handle;
#else
    void *lib_handle;
#endif

    // Where retType is the pointer to a return type of the function
    // This return type can be int, float, double, etc or a struct or class.

    typedef retType* func_t;  

    // load the library -------------------------------------------------
#ifdef WIN32
    string nameOfLibToLoad("C:\opt\lib\libctest.dll");
    lib_handle = LoadLibrary(TEXT(nameOfLibToLoad.c_str()));
    if (!lib_handle) {
        cerr << "Cannot load library: " << TEXT(nameOfDllToLoad.c_str()) << endl;
    }
#else
    string nameOfLibToLoad("/opt/lib/libctest.so");
    lib_handle = dlopen(nameOfLibToLoad.c_str(), RTLD_LAZY);
    if (!lib_handle) {
        cerr << "Cannot load library: " << dlerror() << endl;
    }
#endif

...
...
...

    // load the symbols -------------------------------------------------
#ifdef WIN32
    func_t* fn_handle = (func_t*) GetProcAddress(lib_handle, "superfunctionx");
    if (!fn_handle) {
        cerr << "Cannot load symbol superfunctionx: " << GetLastError() << endl;
    }
#else
    // reset errors
    dlerror();

    // load the symbols (handle to function "superfunctionx")
    func_t* fn_handle= (func_t*) dlsym(lib_handle, "superfunctionx");
    const char* dlsym_error = dlerror();
    if (dlsym_error) {
        cerr << "Cannot load symbol superfunctionx: " << dlsym_error << endl;
    }
#endif

...
...
...

    // unload the library -----------------------------------------------

#ifdef WIN32
    FreeLibrary(lib_handle);
#else
    dlclose(lib_handle);
#endif

10. Tools

ar - create, modify, and extract from archives
ranlib - generate index to archive
nm - list symbols from object files
ld - The GNU linker
ldconfig - configure dynamic linker run-time bindings
ldconfig -p : Print the lists of directories and candidate libraries stored in the current cache.
i.e. /sbin/ldconfig -p | grep libGL
ldd - print shared library dependencies
gcc/g++ - GNU project C and C++ compiler
ld.so - a.out dynamic linker/loader
ld.so, ld-linux.so* - dynamic linker/loader

11. Notes

1. Direct loader to pre-load a specific shared library before all others: export LD_PRELOAD=/usr/lib/libXXX.so.x; exec program. This is specified in the file /etc/ld.so.preload and extended with the environment variable LD_PRELOAD.
直接加载器在所有其他库之前预加载特定的共享库:export LD_PRELOAD=/usr/lib/libXXX.so.x; exec program。这在文件 /etc/ld.so.preload 中指定,并使用环境变量 LD_PRELOAD 进行扩展。

ld.so - a.out dynamic linker/loader
Using LD_PRELOAD libraries and glibc backtrace function for debugging
https://scaryreasoner.wordpress.com/2007/11/17/using-ld_preload-libraries-and-glibc-backtrace-function-for-debugging/

2. Running Red Hat 7.1 (glibc 2.2.2) but compiling for Red Hat 6.2 compatibility.

        export LD_ASSUME_KERNEL=2.2.5
        . /usr/i386-glibc21-linux/bin/i386-glibc21-linux-env.sh

3. Environment variable to highlight warnings, errors, etc (环境变量以突出显示警告、错误等): export CC="colorgcc"

backtrace:n. 回溯,向后追踪

12. YoLinux C/C++ Software Development Tutorials

13. Links

GNU Libtool - The GNU Portable Library Tool
https://www.gnu.org/software/libtool/libtool.html

Program Library HOWTO
http://www.tldp.org/HOWTO/Program-Library-HOWTO/

14. Books

Advanced Linux Programming - Mark Mitchell, Jeffrey Oldham, Alex Samuel, Jeffery Oldham
Linux Programming Bible - John Goerzen

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Yongqiang Cheng

梦想不是浮躁,而是沉淀和积累。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值