当前位置: 首页 > news >正文

Linux共享库,静态库与相关系统调用,工具的使用总结


tags: Linux C Syscall

写在前面

总结Unix/Linux操作系统的共享库/静态库部分, 以及一些系统调用.

参考Linux/UNIX系统编程手册41-42章.

测试程序均在Ubuntu下使用cc(gcc-9)运行成功.

$ gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/9/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none:hsa
OFFLOAD_TARGET_DEFAULT=1
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 9.4.0-1ubuntu1~20.04.1' --with-bugurl=file:///usr/share/doc/gcc-9/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++,gm2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-9 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-plugin --enable-default-pie --with-system-zlib --with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none=/build/gcc-9-Av3uEd/gcc-9-9.4.0/debian/tmp-nvptx/usr,hsa --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.1)

测试代码文件

这里我为了方便就用了C自带的宏命令, __FILE__用于显示当前程序的文件名, __func__用于显示当前执行的函数名.

主要结构就是:

prog调用mod*的函数, mod*中各自实现一个函数.

prog.c

#include <stdio.h>void x1();
void x2();
void x3();int main(void) {printf("%s-%s running...\n", __FILE__, __func__);x1();x2();x3();return 0;
}

mod1.c

#include <stdio.h>
void x1() { printf("%s-%s running...\n", __FILE__, __func__); }

mod2.c

#include <stdio.h>
void x2() { printf("%s-%s running...\n", __FILE__, __func__); }

mod3.c

#include <stdio.h>
void x3() { printf("%s-%s running...\n", __FILE__, __func__); }

注意: 后面会根据实际情况更改代码.

基本概念

构建程序的一种方式是简单地将每一个源文件编译成目标文件,然后将这些目标文件链接在一起组成一个可执行程序. 下面是两种常用的链接目标文件(.o)的方式, 包括静态库(归档文件)和共享库(动态链接库).

静态库

静态库也被称为归档文件(archive), 静态库能带来下列好处:

  1. 可以将一组经常被用到的目标文件组织进单个库文件,这样就可以使用它来构建多个可执行程序并且在构建各个应用程序的时候无需重新编译原来的源代码文件。
  2. 链接命令变得更加简单了。在链接命令行中只需要指定静态库的名称即可,而无需一个个地列出目标文件了。链接器知道如何搜素静态库并将可执行程序需要的对象抽取出来。

静态库实际上就是一个保存所有被添加到其中的目标文件的副本的文件.

一般来说, 静态库被命名为libname.a(Linux下). 后面会介绍创建静态库的具体方式(通过ar命令).

共享库(动态链接库)

共享库是一种将库函数打包成一个单元使之能够在运行时被多个进程共享的技术。这种技术能够节省磁盘空间和RAM.

根据惯例,共享库的前缀为lib,后缀为.so(表示shared object)

静态库实战

使用ar命令创建, 下面是几种常用参数:

  • r: (replace)替换, 用于创建静态库, 将目标文件插入到静态库并取代同名的目标文件.
  • d: (delete)删除, 删除静态库中的指定目标(模块).
  • t: (table)目录表, 输出静态库中所有的目标名称(目录表).
  • v: 与t合用, 用于输出详细信息.

示例: ar命令创建静态库

以上面的四个C源程序为例, 创建名为libstatic.a的静态库, 方法如下:

  1. 生成目标文件:

    cc -c -g mod*.c
    
  2. 创建静态库文件:

    ar r libstatic.a mod*.o
    # ar: creating libstatic.a
    
  3. (可选)删除目标文件:

    rm *.o
    
  4. 查看静态库文件:

    ar tv libstatic.a
    # rw-r--r-- 0/0   6112 Jan  1 08:00 1970 mod1.o
    # rw-r--r-- 0/0   6112 Jan  1 08:00 1970 mod2.o
    # rw-r--r-- 0/0   6112 Jan  1 08:00 1970 mod3.o
    
  5. 删除模块(对应的prog.c)也要相应操作(注释掉x3()的调用):

    ar d libstatic.a mod3.o
    

示例: 使用静态库

使用静态库链接可执行程序.

  1. 编译主程序:

    cc -c -g prog.c
    
  2. 通过静态库生成可执行程序: (下面三种方法都可)

    # cc -g -o prog_static prog.o libstatic.a
    cc -g -o prog_static prog.o -lstatic
    # cc -g -o prog_static prog.o -L. -lstatic # 指定静态库的搜索路径(默认是当前路径`.`)
    
  3. 执行: (prog.c中注释了x3())

    ./prog_static
    # prog.c-main running...
    # mod2.c-x1 running...
    # mod3.c-x2 running...
    

共享库实战

以上面的四个C源程序为例, 创建名为libshared.so的共享库, 并查看相关信息. 方法如下:

示例: 使用cc创建共享库

  1. 编译生成目标文件:

    -fPIC选项指定编译器应该生成位置独立(无关)的代码(Position-independent Code),这会改变编译器生成执行特定操作的代码的方式,包括访问全局、静态和外部变量,访问字符串常量,以及获取函数的地址。这些变更使得代码可以在运行时被放置在任意一个虚拟地址处。这一点对于共享库来讲是必需的,因为在链接的时候是无法知道共享库代码位于内存的何处的。

    一个共享库在运行时所处的内存位置依赖于很多因素,如

    • 加载这个库的程序已经占用的内存数量
    • 这个程序已经加载的其他共享库
    cc -g -c -fPIC -Wall mod*.c
    
  2. 链接生成共享库:

    cc -g -shared -o libshared.so mod*.o
    
  3. 在链接阶段将共享库的名称嵌入可执行文件中:

    cc -g -Wall -o main prog.c libshared.so
    

1+2可以一步到位:

cc -g -fPIC -Wall mod*.c -shared -o libshared.so

后续步骤: 动态链接

创建完之后还要进行一个步骤, 否则执行时会出现:

./main
# 报错:
# ./main: error while loading shared libraries: libshared.so: cannot open shared object file: No such file or directory

动态链接, 其实就是运行时解析内嵌的库名. 由动态链接器完成, 路径为/lib/ld-linux.so.2

$ /lib/ld-linux.so.2
Usage: ld.so [OPTION]... EXECUTABLE-FILE [ARGS-FOR-PROGRAM...]
You have invoked `ld.so', the helper program for shared library executables.
This program usually lives in the file `/lib/ld.so', and special directives
in executable files using ELF shared libraries tell the system's program
loader to load the helper program from this file.  This helper program loads
the shared libraries needed by the program executable, prepares the program
to run, and runs it.  You may invoke this helper program directly from the
command line to load and run an ELF executable file; this is like executing
that file itself, but always uses this helper program from the file you
specified, instead of the helper program file specified in the executable
file you run.  This is mostly of use for maintainers to test new versions
of this helper program; chances are you did not intend to run this program.--list                list all dependencies and how they are resolved--verify              verify that given object really is a dynamically linkedobject we can handle--inhibit-cache       Do not use /etc/ld.so.cache--library-path PATH   use given PATH instead of content of the environmentvariable LD_LIBRARY_PATH--inhibit-rpath LIST  ignore RUNPATH and RPATH information in object namesin LIST--audit LIST          use objects named in LIST as auditors--preload LIST        preload objects named in LIST

动态链接器会检查程序所需的共享库清单并使用一组预先定义好的规则来在文件系统上找出相关的库文件

很多共享库位于/lib/usr/lib中。之所以出现上面的错误消息是因为程序所需的库位于当前工作目录中,而不位于动态链接器搜索的标准目录清单中。

方法1: 指定环境变量LD_LIBRARY_PATH

通知动态链接器一个共享库位于一个非标准目录中的一种方法是将该目录添加到LD_LIBRARY_PATH环境变量中以分号分隔的目录列表中.

那么要想找到路径, 就要指定当前路径为动态链接库的搜索路径:

$ LD_LIBRARY_PATH=. ./main
prog.c-main running...
mod1.c-x1 running...
mod2.c-x2 running...

方法2: (生产环境常用)共享库移动到/usr/local/lib/

$ sudo cp libshared.so /usr/local/lib/libshared.so
$ ./main
prog.c-main running...
mod1.c-x1 running...
mod2.c-x2 running...

方法3: 将共享库目录列表插入可执行程序

在静态编辑阶段可以在可执行文件中插入一个在运行时搜索共享库的目录列表。这种方式对于库位于一个固定的但不属于动态链接器搜索的标准位置的位置中时是非常有用的。

需要给链接器(linker)传入参数-rpath, 使用-Wl选项:

man cc:

-Wl,optionPass option as an option to the linker.  If option contains commas,it is split into multiple options at the commas.  You can use thissyntax to pass an argument to the option.  For example,-Wl,-Map,output.map passes -Map output.map to the linker.  Whenusing the GNU linker, you can also get the same effect with-Wl,-Map=output.map.NOTE: In Ubuntu 8.10 and later versions, for LDFLAGS, the option-Wl,-z,relro is used.  To disable, use -Wl,-z,norelro.

那么, 具体的方法就是: (注意, 是绝对路径)

$ cc -g -Wall -Wl,-rpath=/home/zorch/code/c_cpp_code/library-test -o main prog.c liba.so

为了测试, 删除之前放入/usr/local/lib/的共享库libshared.so.

$ sudo rm /usr/local/lib/libshared.so

测试一下:

$ ./main
prog.c-main running...
mod1.c-x1 running...
mod2.c-x2 running...

共享库版本与别名(soname)

引子:

一般来讲,一个共享库相互连续的两个版本是相互兼容的,这意味着每个模块中的函数对外呈现出来的调用接口是一致的,并且函数的语义是等价的(即它们能取得同样的结果)。这种版本号不同但相互兼容的版本被称为共享库的次要版本。但有时候需要创建创建一个库的新主版本——即与上一个版本不兼容的版本.

版本号规则

  • 主要版本: 主要版本标识符由一个数字构成,这个数字随着库的每个不兼容版本的发布而顺序递增

  • 次要版本: 次要版本标识符可以是任意字符串。但根据惯例,它要么是一个数字,要么是两个由点分隔的数字,其中第一个数字标识出了次要版本,第二个数字表示该次要版本中的补丁号或修订号.

命名规则

需要理解下面的三个名称:

  1. 真实名称: 格式为libname.so.major-id.minor-id;

  2. 别名: soname, 格式为libname.so.major-id;

  3. 链接器名称: 就是链接时制定的名称, 不包含版本号, 只有lib*.so. 将可执行文件与共享库链接起来时会用到这个名称

    有了链接器名称之后就可以构建能够自动使用共享库的正确版本(即最新版本)的独立于版本的链接命令了

三者关系为: (以一个共享库libdevmapper.so.1.02.1为例)
libdevmapper.so⏞链接器名称.1⏟soname.02.1⏟真实名称\underbrace{\underbrace{\overbrace{\text{libdevmapper.so}}^{\large 链接器名称}.1}_{\text{\Large soname}}.02.1}_{\large 真实名称} 真实名称sonamelibdevmapper.so链接器名称.1.02.1

示例: 查看共享库信息

readelf/objdump/nm/ldconfig/ldd/pldd/ld

ld

主要功能是链接器, 但是可以查看共享库的搜索路径:

$ ld --verbose | grep SEARCH
SEARCH_DIR("=/usr/local/lib/x86_64-linux-gnu"); SEARCH_DIR("=/lib/x86_64-linux-gnu"); SEARCH_DIR("=/usr/lib/x86_64-linux-gnu"); SEARCH_DIR("=/usr/lib/x86_64-linux-gnu64"); SEARCH_DIR("=/usr/local/lib64"); SEARCH_DIR("=/lib64"); SEARCH_DIR("=/usr/lib64"); SEARCH_DIR("=/usr/local/lib"); SEARCH_DIR("=/lib"); SEARCH_DIR("=/usr/lib"); SEARCH_DIR("=/usr/x86_64-linux-gnu/lib64"); SEARCH_DIR("=/usr/x86_64-linux-gnu/lib");

直观一点:

$ ld --verbose | grep SEARCH | awk -v FS="=" -v RS="\");" '{print $2}' | sort/lib
/lib64
/lib/x86_64-linux-gnu
/usr/lib
/usr/lib64
/usr/lib/x86_64-linux-gnu
/usr/lib/x86_64-linux-gnu64
/usr/local/lib
/usr/local/lib64
/usr/local/lib/x86_64-linux-gnu
/usr/x86_64-linux-gnu/lib
/usr/x86_64-linux-gnu/lib64

ldconfig

  1. -p查看系统的共享库信息:

    事实上显示的是/etc/ld.so.cache文件内容(格式化输出的内容)
    -p, --print-cache
    Print the lists of directories and candidate libraries stored in
    the current cache.

    $ ldconfig -p | grep liba.soliba.so (libc6,x86-64) => /lib/liba.so
    

ldd/pldd

ldd查看指定程序运行所需的共享库:

$ ldd mainlinux-vdso.so.1 (0x00007ffee634e000)liba.so => /lib/liba.so (0x00007ff8cd512000)libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ff8cd320000)/lib64/ld-linux-x86-64.so.2 (0x00007ff8cd534000)

pldd查看指定进程运行所需的共享库:

$ sleep 10&
[1] 402337$ sudo pldd 402337
402337:	/usr/bin/sleep
linux-vdso.so.1
/lib/x86_64-linux-gnu/libc.so.6
/lib64/ld-linux-x86-64.so.2

objdump/readelf

反汇编工具, 这两个命令都有等价的选项和参数, 需要根据实际情况来选择.

例如, 查看某一模块是否使用了-fPIC选项(使用位置无关代码).

$ cc -g --no-pic -c mod1.c
$ readelf -s mod1.o | grep OFF17: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _GLOBAL_OFFSET_TABLE_

或者通过查看共享库来判断-fPIC的使用:

$ objdump --all-headers libx1.so | grep TEXTREL
$ readelf -d libx1.so | grep TEXTREL

若产生输出就说明至少有一模块未使用-fPIC编译.

新版gcc好像默认不加-fPIC也是会使用-fPIC的, 如果用了--no-pic编译, 之后就无法链接共享库了.

$ cc -g --no-pic -c mod1.c
$ cc -g -Wall -shared -o libx1_wiout_PIC.so mod1.o
/usr/bin/ld: mod1.o: relocation R_X86_64_32 against `.rodata' can not be used when making a shared object; recompile with -fPIC
collect2: error: ld returned 1 exit status

nm

查看目标库或可执行程序中定义的一组符号.

$ nm -A /usr/lib/lib*.so | grep x
/usr/lib/libshared.so:                 w __cxa_finalize@@GLIBC_2.2.5
/usr/lib/libshared.so:00000000000010d0 t __do_global_dtors_aux
/usr/lib/libshared.so:0000000000003e18 d __do_global_dtors_aux_fini_array_entry
/usr/lib/libshared.so:0000000000001119 T x1
/usr/lib/libshared.so:0000000000001143 T x2
/usr/lib/libshared.so:000000000000116d T x3

上面的-A选项指定了在显示符号的每一行的开头处应该列出库的名称。

这样做是有必要的,因为在默认情况下,nm只列出库名一次,然后在后面会列出库中包含的所有符号,这对于像上面那样进行某种过滤的例子来讲是没有用处的

如果不加的话, 就是下面这样:

$ nm /usr/lib/lib*.so | grep xw __cxa_finalize@@GLIBC_2.2.5
00000000000010d0 t __do_global_dtors_aux
0000000000003e18 d __do_global_dtors_aux_fini_array_entry
0000000000001119 T x1
0000000000001143 T x2
000000000000116d T x3

或者用来查看编译器是否使用-fPIC选项构建目标文件.

$ nm mod1.o | grep _GLOBAL_OFFSET_TABLE_U _GLOBAL_OFFSET_TABLE_

示例: 共享库的链式调用

这个名称是我起的, 就是说在一个共享库中还要调用另外的一些共享库才能完成共享库的构建工作.

需要通过-rpath来指定搜索路径.

为了完成这个例子, 这里我们把mod1.c改一下, 让x1()实现为调用x2(), 如下:

// mod1.c (修改)
#include <stdio.h>
void x2();void x1() {printf("%s-%s running...\n", __FILE__, __func__);x2();
}

然后: mkdir dir1 && mv mod1.c dir1, mkdir dir2 && mv mod2.c dir2.

现在测试文件为:

.
├── dir1
│   └── mod1.c
├── dir2
│   └── mod2.c
├── mod3.c // 未用到, 在prog.c中已注释掉
└── prog.c

这时候再生成共享库, 步骤如下:

  1. dir2中:

    cc -g -c -fPIC -Wall mod2.c
    cc -g -shared -o libx2.so mod2.o
    
  2. dir1中:

    -L指定了库的链接时位置.

    cc -g -c -Wall -fPIC mod1.c
    cc -g -shared -o libx1.so mod1.o -Wl,-rpath,/home/zorch/code/c_cpp_code/library-test/dir2 -L/home/zorch/code/c_cpp_code/library-test/dir2 -lx2
    
  3. 在主目录中:

    cc -g -Wall -o main prog.c -Wl,-rpath,/home/zorch/code/c_cpp_code/library-test/dir1,-rpath,/home/zorch/code/c_cpp_code/library-test/dir2 -L/home/zorch/code/c_cpp_code/library-test/dir1 -L/home/zorch/code/c_cpp_code/library-test/dir2 -lx1 -lx2
    
  4. 执行:

    $ ./main
    prog.c-main running...
    mod1.c-x1 running...
    mod2.c-x2 running...
    mod2.c-x2 running...
    

注意事项

  1. 按照书中的方法, 在主目录中执行:

    $ cc -g -Wall -o main prog.c -Wl,-rpath,/home/zorch/code/c_cpp_code/library-test/dir1 -L/home/zorch/code/c_cpp_code/library-test/dir1 -lx1
    /usr/bin/ld: /tmp/cc34q2m6.o: undefined reference to symbol 'x2'
    /usr/bin/ld: /home/zorch/code/c_cpp_code/library-test/dir2/libx2.so: error adding symbols: DSO missing from command line
    collect2: error: ld returned 1 exit status
    

    会报错, 所以要加上-lx2, 以及搜索路径:

    $ cc -g -Wall -o main prog.c -Wl,-rpath,/home/zorch/code/c_cpp_code/library-test/dir1 -L/home/zorch/code/c_cpp_code/library-test/dir1 -L/home/zorch/code/c_cpp_code/library-test/dir2 -lx1 -lx2
    

    但是这样执行的话会出现:

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

    所以还要通过-Wl加上关于libx2.so-rpath:

    $ cc -g -Wall -o main prog.c -Wl,-rpath,/home/zorch/code/c_cpp_code/library-test/dir1,-rpath,/home/zorch/code/c_cpp_code/library-test/dir2 -L/home/zorch/code/c_cpp_code/library-test/dir1 -L/home/zorch/code/c_cpp_code/library-test/dir2 -lx1 -lx2
    

    猜测可能是gcc版本过高的问题.

  2. 可以通过objdump查看写入的路径:

    $ objdump -p main | grep PATHRUNPATH              /home/zorch/code/c_cpp_code/library-test/dir1:/home/zorch/code/c_cpp_code/library-test/dir2$ objdump -p dir1/libx1.so | grep PATHRUNPATH              /home/zorch/code/c_cpp_code/library-test/dir2
    

    或者用readelf查看:

    $ readelf -d main | grep PATH0x000000000000001d (RUNPATH)            Library runpath: [/home/zorch/code/c_cpp_code/library-test/dir1:/home/zorch/code/c_cpp_code/library-test/dir2]$ readelf -d dir1/libx1.so | grep PATH0x000000000000001d (RUNPATH)            Library runpath: [/home/zorch/code/c_cpp_code/library-test/dir2]
    

示例: 运行时符号解析

观察下面这种情况:

创建一个新的程序, main.c, 内容如下:

#include <stdio.h>void x1() { printf("main-x1\n"); }void func();int main(void) {func();return 0;
}

然后创建mod1.c:

#include <stdio.h>void x1() { printf("mod1-x1\n"); }void func() { x1(); }

现在开始为mod1创建共享库, 然后链接入main, 观察运行情况.

# 编译生成目标文件
$ cc -g -c -fPIC -Wall mod1.c
# 创建共享库
$ cc -g -shared -o libmod1.so mod1.o
# 链接, 加入搜索路径
$ cc -g -o main main.c libmod1.so -Wl,-rpath=/home/zorch/code/c_cpp_code/library-test/global-symbol
# 执行
$ ./main
main-x1

说明程序定义的全局符号会覆盖共享库中的符号, 那么如何指定使用共享库中实现的函数呢?

需要修改一下生成共享库时候的参数.

# 编译生成目标文件
$ cc -g -c -fPIC -Wall mod1.c
# 创建共享库, 增加了`-Bsymbolic`选项
$ cc -g -shared -o libmod1.so mod1.o -Wl,-Bsymbolic
# 链接, 加入搜索路径
$ cc -g -o main main.c libmod1.so -Wl,-rpath=/home/zorch/code/c_cpp_code/library-test/global-symbol
# 执行
$ ./main
mod1-x1

-Bsymbolic选项指定了共享库中对全局符号的引用应该优先被绑定到库中的相应定义上.

示例: 同名库的调用顺序

在链接器可以选择名称一样的静态库和共享库时, 即libxxx.a, libxxx.so, 此时会优先使用共享库, 测试如下:

在上面示例程序的基础上, 再创建一个静态库,

$ ar -r libmod1.a mod1.o
ar: creating libmod1.a

注意, 这里需要注释掉main.c中第三行的x1()函数的定义, 否则链接时候会报错:

/usr/bin/ld: ./libmod1.a(mod1.o): in function `x1':
/home/zorch/code/c_cpp_code/library-test/global-symbol/mod1.c:3: multiple definition of `x1'; main.o:/home/zorch/code/c_cpp_code/library-test/global-symbol/main.c:3: first defined here
collect2: error: ld returned 1 exit status

然后使用下面的命令进行链接:

$ cc -g -o main main.c -L. -lmod1
$ ./main
mod1-x1

此时可以重命名一下libmod1.so, 例如mv libmod1.so aa.so(稍后改回来), 然后重新进行链接, 查看程序大小:

$ cc -g -o main_static main.c -L. -lmod1
$ ./main_static
mod1-x1
$ ls -lh
-rwxrwxr-x 1 zorch zorch  19K Feb 16 11:03 libmod1.so*
-rwxrwxr-x 1 zorch zorch  19K Feb 16 11:19 main*
-rwxrwxr-x 1 zorch zorch  21K Feb 16 11:18 main_static*

可以发现静态库占用比较大, 所以路径存在同名库的话, 默认是用共享库的.

那么如何在链接期指定静态库呢?

  1. 使用链接器名, 即libxx.a:

    $ cc -g -o main main.c libmod1.a
    
  2. 使用-static 指定.

    $ cc -g -o main main.c -static -L. -lmod1
    

    这种方法是全部使用静态库, 程序比较大.

    -rwxrwxr-x 1 zorch zorch 856K Feb 16 11:39 main*
    
  3. 使用-Wl,-Bstatic指定静态库:

    $ cc -g -o main main.c -Wl,-Bstatic -L. -lmod1
    /usr/bin/ld: cannot find -lgcc_s
    /usr/bin/ld: cannot find -lgcc_s
    collect2: error: ld returned 1 exit status
    

    出现这个问题主要是libgcc_s这个库只有共享库版本, 而没有静态库版, 所以会找不到.

共享库进阶

动态加载库(dlopen系统调用集)

使用dlopen系列系统调用, 可以延迟加载共享库, 即程序运行时才打开共享库, 根据名字在共享库中搜索待调用的函数, 然后进行调用. 这样的共享库称为动态加载库.

核心API如下:

  • dlopen(): 打开共享库, 返回句柄(指针).
  • dlsym(): 搜索共享库中的符号(包含函数或变量的字符串), 返回地址(函数指针).
  • dlclose(): 关闭由dlopen()打开的共享库.
  • dladdr(): 获取与加载的符号相关的信息.
  • dlerror(): 返回错误消息字符串. (用于获取执行时出现的错误消息)

使用上述API, 需要指定链接库-ldl, 链接libdl库.

示例: 使用共享库系统调用读取共享库信息/执行模块函数

#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>int main(int argc, char *argv[]) {void *libHandle;void (*funcp)(void);const char *err;libHandle = dlopen(argv[1], RTLD_LAZY);if (!libHandle) fprintf(stderr, "dlopen: %s\n", dlerror());// clear dlerror(void)dlerror();// 下面三种调用等价/* *(void **)(&funcp) = dlsym(libHandle, argv[2]); *//* funcp = dlsym(libHandle, argv[2]); */funcp = (void (*)(void))dlsym(libHandle, argv[2]);err = dlerror();if (err != NULL) fprintf(stderr, "dlsym: %s\n", err);if (!funcp)printf("%s is NULL\n", argv[2]);else(*funcp)(); // exec function in moduledlclose(libHandle);exit(EXIT_SUCCESS);return 0;
}

执行:

$ ./dlopen_test.out dir1/libx1.so x1
mod1.c-x1 running...
mod2.c-x2 running...

示例: 使用dladdr获取与加载符号相关的信息

稍微修改一下上面的程序即可:

#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>int main(int argc, char* argv[]) {void* libHandle;void (*funcp)(void);const char* err;libHandle = dlopen(argv[1], RTLD_LAZY);if (!libHandle) fprintf(stderr, "dlopen: %s\n", dlerror());// clear dlerror(void)dlerror();/* *(void **)(&funcp) = dlsym(libHandle, argv[2]); *//* funcp = dlsym(libHandle, argv[2]); */funcp = (void (*)(void))dlsym(libHandle, argv[2]);err = dlerror();if (err != NULL) fprintf(stderr, "dlsym: %s\n", err);/* typedef struct { *//*     const char* dli_fname; *//*     void* dli_fbase; *//*     const char* dli_sname; *//*     void* dli_saddr; *//* } Dl_info; */Dl_info info;if (!funcp)printf("%s is NULL\n", argv[2]);else {// exec(*funcp)();if (dladdr(funcp, &info)) {printf("dli_fname: %s\n", info.dli_fname);printf("dli_fbase: %p\n", info.dli_fbase);printf("dli_sname: %s\n", info.dli_sname);printf("dli_saddr: %p\n", info.dli_saddr);printf("call func by dli_saddr: \n");((void (*)(void))(info.dli_saddr))(); // func ptr cast}}dlclose(libHandle);exit(EXIT_SUCCESS);return 0;
}

运行:

$ ./dladdr_test.out dir1/libx1.so x1
mod1.c-x1 running...
mod2.c-x2 running...
dli_fname: dir1/libx1.so
dli_fbase: 0x7fef52f36000 # 加载共享库的基地址
dli_sname: x1
dli_saddr: 0x7fef52f37139
call func by dli_saddr:
mod1.c-x1 running...
mod2.c-x2 running...

控制符号可见性

需要控制API调用的可见性, 避免使用者程序出现不兼容问题, 通过gcc提供的特有声明来完成, 如下:

gcc特性声明

void
__attribute__((visibility("hidden")))
func(void) { /* code */
}

通过下面这种方法就可以使符号不可见: (区别于static关键字)

static将一个符号的可见性限制在单个源码文件中

hidden属性(特性)将一个符号对构成共享库的所有源码文件都可见, 但是对库之外的文件不可见.

下面是一个测试:

// test_hidden.c
void t1(void);int main(int argc, char *argv[]) {t1();return 0;
}
// hidden.c
#include <stdio.h>void
// __attribute__((visibility("hidden")))
t1(void) { printf("unreachable\n"); }

首先是不加特性的版本:

$ cc -g -shared -o libhidden.so special_attr_hidden.c
$ cc -g -Wall -Wl,-rpath=/home/zorch/code/c_cpp_code/library-test -o main test_hidden.c libhidden.so
$ ./main
unreachable

解注释, 使特性生效:

$ cc -g -shared -o libhidden.so special_attr_hidden.c
$ cc -g -Wall -Wl,-rpath=/home/zorch/code/c_cpp_code/library-test -o main test_hidden.c libhidden.so
/usr/bin/ld: /tmp/cc7EpQgY.o: in function `main':
/home/zorch/code/c_cpp_code/library-test/test_hidden.c:4: undefined reference to `t1'
collect2: error: ld returned 1 exit status

可见hidden特性生效了, 使得共享库内API函数的外部调用失败.

后记

书中还有版本脚本等的内容, 这里不详细列出了.

相关文章:

Linux共享库,静态库与相关系统调用,工具的使用总结

tags: Linux C Syscall 写在前面 总结Unix/Linux操作系统的共享库/静态库部分, 以及一些系统调用. 参考Linux/UNIX系统编程手册41-42章. 测试程序均在Ubuntu下使用cc(gcc-9)运行成功. $ gcc -v Using built-in specs. COLLECT_GCCgcc COLLECT_LTO_WRAPPER/usr/lib/gcc/x86_64…...

「JVM 编译优化」javac 编译器源码解读

Java 的编译过程 前端编译: 编译器的前端&#xff0c;将 Java 文件转变成 Class 文件的过程&#xff1b;如 JDK 的 javac、Eclipse JDT 中的增量式编译器 ECJ&#xff1b;即使编译: JIT&#xff0c;Just In Time Compiler&#xff0c;在运行期将字节码转变成本地机器码的过程&…...

Leetcode DAY 34: K次取反后最大化的数组和 and 加油站 and 分发糖果

1005.K次取反后最大化的数组和 class Solution:def largestSumAfterKNegations(self, nums: List[int], k: int) -> int:nums sorted(nums, key abs, reverse True)for i in range(len(nums)):if nums[i] < 0:nums[i] -nums[i]k - 1else:continueif k 0:return sum(…...

2023美赛A题思路

在线解析 https://kdocs.cn/l/ccNGjN9sGugL​kdocs.cn/l/ccNGjN9sGugL A题思路&#xff1a;&#xff08;具体以题目解决问题顺序为主&#xff09; 这道题分析植被就行&#xff0c;主要涉及不同植被间的相互作用&#xff0c;有竞争有相互促进&#xff0c;我查了下“植物科学数…...

前端上传文件

前言 以 vue 举例&#xff0c;原生 html css js 现在应该很少有人去写了 一、绘制样式 绘制两个标签&#xff0c;一个 <div></div> &#xff0c;一个 <input type"file" />&#xff1b; 为 <div></div>添加 css 样式&#xff0c…...

后台管理系统中选项卡的动态渲染

动态渲染选项卡其中router-link是为了当点击选项卡时跳转到选项卡所在的列表选项卡需要动态渲染&#xff0c;其中active是当选中后激活选中的样式为图标添加点击删除事件在状态机配置tabMenu&#xff08;为了动态渲染&#xff09;需要在tabMenu添加&#xff1a;active、title、…...

网络层重点协议之IP协议(IPv4)

网络层的作用就是来路由的选择&#xff0c;规划传输的路径&#xff0c;其中网络层的重点协议就是IP协议。4位版本号版本号的取值只有4和64位首部长度描述了IP报头有多长&#xff0c;报头中有一个选项部分&#xff0c;是变长的&#xff0c;是可有可无的部分&#xff0c;所以IP报…...

CentOS Stream 8配置DNS

1&#xff1a;用CentOS搭建DNS的目的是想解析一台下载服务器&#xff0c;IP地址172.18.0.58&#xff0c;现在是用IP地址方的式访问&#xff0c;想搭建DNS服务器用域名的方式访问。 使用下面的命令查看一下当前系统的Bind版本。 yum info bind 版本是9.11.36.我的CentOS是最小…...

【roLabelImg】windows下旋转框标注软件安装、使用、rolabelimg打包成exe

主要参考&#xff1a; roLabelImg安装、使用、数据格式roLabelImg在Win10系统下打包成exe - 问雪的文章 - 知乎 一、安装 1.1 直接下载exe运行 劝大家直接去下别人编译好的吧&#xff0c;本来是训练模型标记的&#xff0c;结果搞了半天去了解这个软件了&#xff0c;哎~ 我…...

2023美赛F题:绿色经济

文章目录背景要求词汇表背景 国内生产总值&#xff08;GDP&#xff09;可以说是最知名且最常用的衡量一个国家经济健康的指标之一。它通常用于确定一个国家的购买力和贷款能力&#xff0c;为国家提出提高GDP的政策和项目提供了动力。GDP “衡量一个国家在一段特定时间内生产的…...

华为OD机试 - 剩余可用字符集 | 备考思路,刷题要点,答疑 【新解法】

最近更新的博客 【新解法】华为OD机试 - 关联子串 | 备考思路,刷题要点,答疑,od Base 提供【新解法】华为OD机试 - 停车场最大距离 | 备考思路,刷题要点,答疑,od Base 提供【新解法】华为OD机试 - 任务调度 | 备考思路,刷题要点,答疑,od Base 提供【新解法】华为OD机试…...

“笨办法”学Python 3 ——练习 40. 模块、类和对象

练习40 模块、类和对象 知识点&#xff1a; 40.1.0 模块就像字典 my_stuff[apple] #my_stuff是字典&#xff0c;访问字典apple键的值 import mystuff mystuff.apple() #mystuff是模块&#xff0c;模块访问函数apple() print(mystuff.tangerine) #模块访问变量tangerine说明P…...

自动驾驶:BEVDet

自动驾驶&#xff1a;BEVDetIntroductionMethodoloData AugmentationNetwork StructureScale-NMS实验Introduction 作者通过现有的算法&#xff08;LSS&#xff09;、独特的数据增强方案与新的NMS方案整合了一个BEV框架&#xff08;BEVDet&#xff09;。 如下图&#xff1a; …...

vue的组件通信

文章目录3. 组件通信3.1 父组件-->子组件3.3组件自定义事件&#xff08;子->父&#xff09;3.4.全部事件总线&#xff08;两代以上&#xff09;3.5消息的订阅与发布3. 组件通信 3.1 父组件–>子组件 <Student name"张三" :age"18"></St…...

Typescript的定义及使用优势

编程语言的类型&#xff1a; 动态类型语言 (Dynamically Typed Language&#xff09;静态类型语言 (Statically Typed Language&#xff09; 两种语言的含义及区别&#xff1a; 比如JS、python就是动态类型语言&#xff0c;什么是动态类型语言&#xff0c;通俗的讲&#xff0…...

正则验证:手机号码验证

<!DOCTYPE html> <html> <head> <meta charset"utf-8"> <title></title> </head> <body> 手机号码<input type"text" id"phone"> <span…...

视频融合 flv流格式对接(上)

FLV 是FLASH VIDEO的简称&#xff0c;FLV流媒体格式是随着Flash MX的推出发展而来的视频格式。由于它形成的文件极小、加载速度极快&#xff0c;使得网络观看视频文件成为可能&#xff0c;它的出现有效地解决了视频文件导入Flash后&#xff0c;使导出的SWF文件体积庞大&#xf…...

提问:影视剪辑解说都是怎样配音的,软件合成还是自己配音?

“影视剪辑解说都是怎样配音的&#xff0c;软件合成还是自己配音&#xff1f;”这是一个很好的问题并且困扰着很多人&#xff0c;因为不知道该如何选择。究竟应该使用软件来完成配音工作呢?还是自己动手配音呢&#xff1f;这是一个很难回答的问题。如果你问我的话&#xff0c;…...

基于RK3588的嵌入式linux系统开发(二)——uboot源码移植及编译

由于官方的SDK占用空间较大&#xff08;大约20GB左右&#xff09;&#xff0c;需要联系相关供应商提供&#xff0c;且官方的SDK通过各种脚本文件进行集成编译&#xff0c;难以理解系统开发的详细过程。本章介绍直接从官方Github网站下载源码进行移植&#xff0c;进行uboot移植及…...

excel报表技巧:几个关于汇报演示方面的小功能

年终了&#xff0c;总结汇报避免不了。如果你的PPT还不够好&#xff0c;那就直接用Excel做汇报吧~这里有5条小技巧&#xff0c;可以帮助你最高效地展示自己的成绩报表&#xff01;想象一下&#xff0c;用SHIFTCTRLF1全屏显示你的工作表&#xff0c;配合上CtrlPageDown进行工作表…...

【数据结构与算法】Manacher算法

&#x1f320;作者&#xff1a;阿亮joy. &#x1f386;专栏&#xff1a;《数据结构与算法要啸着学》 &#x1f387;座右铭&#xff1a;每个优秀的人都有一段沉默的时光&#xff0c;那段时光是付出了很多努力却得不到结果的日子&#xff0c;我们把它叫做扎根 目录&#x1f449;…...

【CMake】CMake构建C++代码(一)

在Linux开发过程中&#xff0c;难免会用到CMake来构建你的代码。本文将说明如何构建自己的代码&#xff0c;将自己的代码变为共享库&#xff0c;共其他代码使用。 文章目录在Linux开发过程中&#xff0c;难免会用到CMake来构建你的代码。本文将说明如何构建自己的代码&#xff…...

让我们,从头到尾,通透I/O模型

什么是IO 一句话总结 IO就是内存和硬盘的输入输出 I/O 其实就是 input 和 output 的缩写&#xff0c;即输入/输出。 那输入输出啥呢&#xff1f; 比如我们用键盘来敲代码其实就是输入&#xff0c;那显示器显示图案就是输出&#xff0c;这其实就是 I/O。 而我们时常关心的磁盘…...

Word控件Spire.Doc 【Table】教程(16):C#/VB.NET:在 Word 表格中插入或提取图像

Spire.Doc for .NET是一款专门对 Word 文档进行操作的 .NET 类库。在于帮助开发人员无需安装 Microsoft Word情况下&#xff0c;轻松快捷高效地创建、编辑、转换和打印 Microsoft Word 文档。拥有近10年专业开发经验Spire系列办公文档开发工具&#xff0c;专注于创建、编辑、转…...

C++如何实现系统语言切换功能,MessageBox的确认/取消按钮语言显示如何跟程序一致

文章目录前言 一、新建工程二、添加多国语言的资源三、程序语言设置四、语言切换五、字符串处理六、MessageBox的问题七、相关函数和类型参考文章前言 目前很多软件都是要出口到多个国家&#xff0c;多个地区&#xff0c;因此&#xff0c;为软件提供多国语言支持就成为了一个基…...

计算机组成原理学习笔记:循环冗余校验码

循环冗余校验码 CRC 码 循环冗余校验码 (cyclic redundancy Check, CRC) 十进制除法 从熟悉的十进制出发&#xff0c;假设现在你要给另一个人传送882这样的一个10进制数据&#xff0c;为了防止传送数据的过程中某一个数据发生错误你可以和你的另一个小伙伴约定一个除数&…...

Educational Codeforces Round 143 (Rated for Div. 2) A — C

Educational Codeforces Round 143 (Rated for Div. 2) 文章目录A. Two Towers题目大意题目分析codeB. Ideal Point题目大意题目分析codeC. Tea Tasting题目大意题目分析codeA. Two Towers 题目大意 有两个有红蓝两种颜色组成的塔&#xff0c;每次操作可以将其中一个塔顶的色…...

【Unity VR开发】结合VRTK4.0:将浮点数从交互器传递到可交互对象

语录&#xff1a; 愿你熬得过万丈孤独&#xff0c;藏得下星辰大海。 前言&#xff1a; 默认情况下&#xff0c;交互器只能将单个布尔操作传递给可交互对象&#xff0c;后者控制可交互对象上的抓取操作。在其他时候&#xff0c;交互器中的其他操作可能希望传递给可交互对象&…...

【图像分类】基于PyTorch搭建卷积神经网络实现MNIST手写数字识别(附项目完整代码)

写在前面: 首先感谢兄弟们的关注和订阅,让我有创作的动力,在创作过程我会尽最大能力,保证作品的质量,如果有问题,可以私信我,让我们携手共进,共创辉煌。 在【图像分类】基于PyTorch搭建LSTM实现MNIST手写数字体识别(单向LSTM,附完整代码和数据集)文章中,我们使用了…...

4.4 MQC

1. 实验目的 熟悉MQC的应用场景掌握MQC的配置方法2. 实验拓扑 实验拓扑如图4-10所示: 图4-10:MQC 3. 实验步骤 (1) IP地址的配置 AR1的配置 <Huawei>system-view...

政府网站建设专业/合肥网站推广优化

文章目录环境部署组件介绍启动skywalking目录功能及介绍修改数据存储方式指定为elasticsearch7指定为mysql修改默认端口:Java项目启动并绑定skywalking自定义链路追踪&#xff1a;自定义意义&#xff1a;性能剖析:skywalking链路追踪与logback日志整合告警功能告警规则配置项的…...

做内贸注册什么网站/如何在百度上推广业务

剧本基本剧情&#xff1a; 小H&#xff0c;某集团智能机械体一号员工 &#xff0c;喜欢户外运动&#xff0c;擅长武术 &#xff0c;街舞&#xff0c;吊儿郎当 小S (众)&#xff0c;不详 尼泊尔 EBC 探险之路 遭遇China 国 在尼泊尔赌品交易 受中国政府之命 捣毁 临危受命 …...

100t空间 做网站/广州seo

跨浏览器的事件处理程序 DOM0级处理事件 将一个函数赋值给一个事件处理程序属性 事件流&#xff1a; 冒泡阶段 使用&#xff1a; 1、为元素增加事件&#xff1a; let btn document.getElementById("myBtn"); btn.onclick function(){alert(this.id); } 复制代码2、…...

政府网站后台如何管理/网络广告策划方案

一、&#xff1e;&#xff1e;和&#xff1c;&#xff1c;运算符的计算方法 举个例子&#xff1a; >> 右移除以取整&#xff1a; 10>> 1的结果是5&#xff0c;计算方法&#xff1a;10/&#xff08;2&#xff09;5 取整后等于5 10>>2 的结果是2&#xff…...

撤销网站备案表填写后/网络营销公司排名

一、腾讯云CenOS上安装MySQL 1.进行事先检测MySQL是否安装。 安装前&#xff0c;我们可以检测系统是否自带安装 MySQL&#xff0c;如果自带安装&#xff0c;那么卸载原来安装的MySQL #rpm -qa | grep mysql若无信息显示则未安装。 若系统有显示信息&#xff0c;通过如下命令将…...

网站的建设及推广/网络营销活动案例

IDEA配置GIt版本控制工具在配置之前&#xff0c;需要先说明的是本机已经安装好了git版本控制工具。在菜单上上选择File&#xff0c;在下拉菜单上选择Settings在左侧列表中选择Version Control&#xff0c;在下面选择Git选择git安装目录中的bin目录下的git.exe配置完毕之后&…...