zoukankan      html  css  js  c++  java
  • TLPI读书笔记第41章-共享库基础2

    41.5 使用共享库的有用工具

    本节将简要介绍对分析共享库、可执行文件以及编译过的目标文件( .o)有用的一组工具。

    ldd 命令

    #ldd(1)(列出动态依赖)命令显示了一个程序运行所需的共享库,如下所示。
    ldd prog
    objdump 和 readelf 命令

    objdump 命令能够用来获取各类信息——包括反汇编的二进制机器码——从一个可执行文件、编译过的目标以及共享库中。它还能够用来显示这些文件中各个 ELF 节的头部信息, 当这样使用 objdump 时它就类似于 readelf, readelf 能显示类似的信息,但显示格式不同。本章结尾处将会列出更多有关 objdump 和 readelf 的信息源。

    nm 命令

    nm 命令会列出目标库或可执行程序中定义的一组符号。这个命令的一种用途是找出哪些库定义了一个符号。如要找出哪个库定义了 crypt()函数则可以像下面这样做。

    nm -A /usr/lib/lib*.so 2>/dev/null |grep ' crypt$'

    nm 的–A 选项指定了在显示符号的每一行的开头处应该列出库的名称。这样做是有必要的,因为在默认情况下, nm 只列出库名一次,然后在后面会列出库中包含的所有符号,这对于像上面那样进行某种过滤的例子来讲是没有用处的。此外,这里还丢弃了标准错误输出以便隐藏与 nm 命令无法识别文件格式有关的错误消息。从上面的输出中可以看出, crypt()被定义在了 libcrypt 库中。

    41.6 共享库版本和命名规则

    下面考虑在共享库的版本化过程中需要做的事情。一般来讲,一个共享库相互连续的两个版本是相互兼容的,这意味着每个模块中的函数对外呈现出来的调用接口是一致的,并且函数的语义是等价的(即它们能取得同样的结果)。这种版本号不同但相互兼容的版本被称为共享库的次要版本。但有时候需要创建创建一个库的新主版本——即与上一个版本不兼容的版本。 同时,必须要确保使用老版本的库的程序仍然能够运行。 为了满足这些版本化的要求,共享库的真实名称和 soname 必须要使用一种标准的命名规范。

    真实名称、 soname 以及链接器名称

    共享库的每个不兼容版本是通过一个唯一的主要版本标识符来区分的,这个主要版本标识符是共享库的真实名称的一部分。根据惯例,主要版本标识符由一个数字构成,这个数字随着库的每个不兼容版本的发布而顺序递增。除了主要版本标识符之外,真实名称还包含一个次要版本标识符,它用来区分库的主要版本中兼容的次要版本。真实名称的格式规范为 libname.so.major-id.minor-id 与主要版本标识符一样,次要版本标识符可以是任意字符串。但根据惯例,它要么是一个数字,要么是两个由点分隔的数字,其中第一个数字标识出了次要版本,第二个数字表示该次要版本中的补丁号或修订号。 共享库的 soname 包括相应的真实名称中的主要版本标识符,但不包含次要版本标识符。因此 soname 的形式为 libname.so.major-id。 通常,会将 soname 创建为包含真实名称的目录中的一个相对符号链接。下面是一些soname 的例子以及它们可能通过符号链接指向的真实名称。 对于共享库的某个特定的主要版本来讲,可能存在几个库文件,这些库文件是通过不同的次要版本标识符来区分的。通常,每个库的主要版本的 soname 会指向在主要版本中最新的次要版本(如上面的 libdemo.so 例子所示)。这种配置使得在共享库的运行时操作期间版本化语义能够正确工作。由于静态链接阶段会将 soname 的副本(独立于次要版本)嵌入到可执行文件中并且 soname 符号链接后面可能会被修改指向一个更新的(次要)版本的共享库,因此可以确保可执行文件在运行时能够加载库的最新的次要版本。此外,由于一个库的不同的主 要版本的 soname 不同,因此它们能够和平地共存并且被需要它们的程序访问。 除了真实名称和 soname 之外,通常还会为每个共享库定义第三个名称:链接器名称,将可执行文件与共享库链接起来时会用到这个名称。链接器名称是一个只包含库名同时不包含主要或次要版本标识符的符号链接,因此其形式为 libname.so。有了链接器名称之后就可以构建能够自动使用共享库的正确版本(即最新版本)的独立于版本的链接命令了。 一般来讲,链接器名称与它所引用的文件位于同一个目录中,它既可以链接到真实名称,也可以连接到库的最新主要版本的 soname。 通常, 最好使用指向 soname 的链接, 因此对 soname所做的变更会自动反应到链接器名称上。 (在 41.7 节中会看到 ldconfig 程序将保持 soname 最新的任务自动化了,因此如果使用了刚才介绍的规范的话就是隐式地维护链接器名称。

    使用标准规范创建一个共享库

    根据上面介绍的相关知识,下面开始介绍如何遵循标准规范来构建一个演示库。首先需要创建目标文件。

    gcc -g -c -fPIC -Wall mod1.c mod2.c mod3.c

    接着创建共享库,其真实名称为 libdemo.so.1.0.1, soname 为 libdemo.so.1。

    gcc -g -shared -Wl,-soname,libdemo.so.1 -o libdemo.so.1.0.1 mod1.o mod2.o mod3.o

    接着为 soname 和链接器名称创建恰当的符号链接。

    ln -s libdemo.so.1.0.1 libdemo.so.1
    ln -s libdemo.so.1 libdemo.so

    接着可以使用 ls 来验证配置(使用 awk 来选择感兴趣的字段)。

    ls -l libdemo.so*|awk '{print $1,$9,$10,$11}'

    接着可以使用链接器名称来构建可执行文件(注意链接命令不会用到版本号),并照常运 行这个程序

    gcc -g -Wall -o prog prog.c -L. -ldemo
    LD_LIBRAPY_PATH=. ./prog

     

    41.7 安装共享库

    在本章到目前为止介绍的例子中都是将共享库创建在用户私有的目录中,然后使用LD_LIBRARY_PATH 环境变量来确保动态链接器会搜到该目录。特权用户和非特权用户都可 以使用这种技术,但在生产应用程序中不应该采用这种技术。一般来讲,共享库及其关联的符号链接会被安装在其中一个标准库目录中,标准库目录包括: 1./usr/lib,它是大多数标准库安装的目录。

    2./lib,应该将系统启动时用到的库安装在这个目录中(因为在系统启动时可能还没有挂载/usr/lib)。

    3./usr/local/lib,应该将非标准或实验性的库安装在这个目录中(对于/usr/lib 是一个由多个系统共享的网络挂载但需要只在本机安装一个库的情况则可以将库放在这个目录中)。 4.其中一个在/etc/ld.so.conf(稍后介绍)中列出的目录。 在大多数情况下,将文件复制到这些目录中需要具备超级用户的权限。 安装完之后就必须要创建 soname 和链接器名称的符号链接了,通常它们是作为相对符号链接与库文件位于同一个目录中。因此要将本章的演示库安装在/usr/lib(只允许 root 进行更新)中则可以使用下面的命令。

    mv libdemo.so.1.0.1 /usr/lib
    cd /usr/lib
    ln -s libdemo.so.1.0.1 libdemo.so.1
    ln -s libdemo.so.1 libdemo.so

    shell 会话中的最后两行创建了 soname 和链接器名称的符号链接

    ldconfig

    ldconfig(8)解决了共享库的两个潜在问题。 1.共享库可以位于各种目录中,如果动态链接器需要通过搜索所有这些目录来找出一个库并加载这个库,那么整个过程将非常慢。 2.当安装了新版本的库或者删除了旧版本的库,那么 soname 符号链接就不是最新的。

    ldconfig 程序通过执行两个任务来解决这些问题。

    1.它搜索一组标准的目录并创建或更新一个缓存文件/etc/ld.so.cache 使之包含在所有这些目录中的主要库版本(每个库的主要版本的最新的次要版本)列表。动态链接器在运行时解析库名称时会轮流使用这个缓存文件。 为了构建这个缓存, ldconfig 会搜索在/etc/ld.so.conf中指定的目录,然后搜索/lib 和 /usr/lib。 /etc/ld.so.conf 文件由一个目录路径名(应该是绝对路径名)列表构成,其中路径名之间用换行、空格、制表符、逗号或冒号分隔。在一些发行版中, /usr/local/lib 目录也位于这个列表中。(如果不在这个列表中,那么就需要手工 将其添加到列表中。 ) 命令 ldconfig –p 会显示/etc/ld.so.cache 的当前内容 2. 它检查每个库的各个主要版本的最新次要版本(即具有最大的次要版本号的版本)以找出嵌入的 soname,然后在同一目录中为每个 soname 创建(或更新)相对符号链接。

    为了能够正确执行这些动作, ldconfig 要求库的名称要根据前面介绍的规范来命名(即库的真实名称包含主要和次要标识符,它们随着库的版本的更新而恰当的增长)。 在默认情况下, ldconfig 会执行上面两个动作,但可以使用命令行选项来指定它执行其中一个动作: -N 选项会防止缓存的重建, -X 选项会阻止 soname 符号链接的创建。 此外, -v (verbose)选项会使得 ldconfig 输出描述其所执行的动作的信息

    每当安装了一个新的库,更新或删除了一个既有库,以及/etc/ld.so.conf 中的目录列表被修改之后,都应该运行 ldconfig。 下面是一个使用 ldconfig 的例子。假设需要安装一个库的两个不同的主要版本,那么需要做下面的事情。

    mv libdemo.so.1.0.1 libdemo.so.2.0.0 /usr/lib
    ldconfig -v |grep libdemo

    上面对 ldconfig 的输出进行了过滤,这样读者就只会看到与名为 libdemo 的库相关的信息了。 接着列出在/usr/lib 目录中名为 libdemo 的文件来验证 soname 符号链接的设置。

    cd /usr/lib 
    ls -l libdemo* |awk '{$1,$$9,$10,$11}'

    还需要为链接器名称创建符号链接,如下面的命令所示。

    ln -s libdemo.so.2 libdemo.so

    如果安装了库的一个新的 2.x 次要版本,那么由于链接器名称指向了最新的 soname,因此 ldconfig 还能取得保持链接器名称最新的效果,如下面的例子所示。

    mv libdemo.so.2.0.1  /usr/lib
    ldconfig -v |grep libdemo

    如果创建和使用的是一个私有库(即没有安装在上述标准目录中的库),那么可以通过使用-n 选项让 ldconfig 创建 soname 符号链接。这个选项指定了 ldconfig 只处理在命令行中列出的目录中的库,而无需更新缓存文件。下面的例子使用了 ldconfig 来处理当前工作目录中的库。

    gcc -g -c -fPIC -Wall mod1.c mod2.c mod3.c
    gcc -g shared -Wl,-soname,libdemo.so.1 -o libdemo.so.1.0.1 mod1.o mod2.o mod3.o
    /sbin/ldconfig -nv .
    ls -l libdemo.so* |awk '{$1,$$9,$10,$11}'

    在上面的例子中,当运行 ldconfig 时指定了完全路径名,因为使用的是一个非特权账号,其 PATH 环境变量不包含/sbin 目录

    41.8 兼容与不兼容库比较

    随着时间的流逝,可能需要修改共享库的代码。这种修改会导致产生一个新版本的库,这个新版本可以与之前的版本兼容,也可能与之前的版本不兼容。如果是兼容的话则意味着 只需要修改库的真实名称的次要版本标识符即可,如果是不兼容的话则意味着必须要定义一个库的新主要版本。 当满足下列条件时表示修改过的库与既有库版本兼容。 1.库中所有公共方法和变量的语义保持不变。换句话说,每个函数的参数列表不变并且对全局变量和返回参数产生的影响不变,同时返回同样的结果值。因此提升性能或修复 Bug(导致更加行为更加符合规定)的变更可以认为是兼容的变更。 2.没有删除库的公共 API 中的函数和变量, 但向公共 API 中添加新函数和变量不会影响兼容性。 3.在每个函数中分配的结构以及每个函数返回的结构保持不变。类似的,由库导出的公共结构保持不变。这个规则的一个例外情况是在特定情况下,可能会向既有结构的结尾处添加新的字段, 但当调用程序在分配这个结构类型的数组时会产生问题。 有时候,库的设计人员会通过将导出结构的大小定义为比库的首个发行版所需的大小大来解决这个问题,即增加一些填充字段以备将来之需。 如果所有这些条件都得到了满足,那么在更新新库名时就只需要调整既有名称中的次要版本号了,否则就需要创建库的一个新主要版本。

    41.9 升级共享库

    共享库的优点之一是当一个运行着的程序正在使用共享库的一个既有版本时也能够安装库的新主要版本或次要版本。在安装的过程中需要做的事情包括创建新的库版本、将其安装在恰当的目录中以及根据需要更新 soname 和链接器名称符号链接(或通常让 ldconfig 来完成这部分工作)。

    如要创建共享库/usr/lib/libdemo.1.0.1 的一个新次要版本,那么需要完成:

    gcc -g -c -fPIC -Wall mod1.c mod2.c mod3.c
    gcc -g -shared -Wl,-soname,libdemo.so.1 -o libdemo.so.1.0.2 mod1.o mod2.o mod3.o
    mv libdemo.so.1.0.2 /usr/lib
    ldconfig -v |grep libdemo

    假设已经正确地配置了链接器名称(即指向库的 soname), 那么就无需修改链接器名称了。 已经运行着的程序会继续使用共享库的上一个次要版本,只有当它们终止或重启之后才会使用共享库的新次要版本。 如果后面需要创建共享库的一个新主要版本( 2.0.0),那么就需要完成:

    gcc -g -c -fPIC -Wall mod1.c mod2.c mod3.c
    gcc -g -shared -Wl,-soname,libdemo.so.2 -o libdemo.so.2.0.0 mod1.o mod2.o mod3.o
    mv libdemo.so.2.0.0 /usr/lib
    ldconfig -v |grep libdemo
    cd /usr/lib
    ln -sf libdemo.so.2 libdemo.so

    从上面的输出可以看出, ldconfig 自动为新主要版本创建了一个 soname 符号链接,但从最后一条命令可以看出,必须要手工更新链接器名称的符号链接。

    41.10 在目标文件中指定库搜索目录

    到 目 前 为 止 本 章 已 经 介 绍 了 两 种 通 知 动 态 链 接 器 共 享 库 的 位 置 的 方 式 : 使 用LD_LIBRARY_PATH 环境变量和将共享库安装到其中一个标准库目录中( /lib、 /usr/lib 或在/etc/ld.so.conf 中列出的其中一个目录)。 还存在第三种方式: 在静态编辑阶段可以在可执行文件中插入一个在运行时搜索共享库的目录列表。 这种方式对于库位于一个固定的但不属于动态链接器搜索的标准位置的位置中时是非常有用的。要实现这种方式需要在创建可执行文件时使用-rpath 链接器选项。

    gcc -g -Wall -Wl,-rpath,/home/mtk/pdir -o prog prog.c libdemo.so

    上面的命令将字符串/home/mtk/pdir 复制到了可执行文件 prog 的运行时库路径( rpath)列表中,因此当运行这个程序时,动态链接器在解析共享库引用时还会搜索这个目录。 如果有必要的话,可以多次指定–rpath 选项;所有这些列出的目录会被连接成一个放到可执行文件中的有序 rpath 列表。或者,在一个 rpath 选项中可以指定多个由分号分割开来的目录列表。在运行时,动态链接器会按照在–rpath 选项中指定的目录顺序来搜索目录

    在构建共享库时使用–rpath 链接器选项

    在构建共享库时–rpath 选项也是有用的。假设有一个依赖于另一个共享库 libx2.so 的共享库 libx1.so,如图 41-4 所示。另外再假设这些库分别位于非标准目录 d1 和 d2 中。下面介绍构建这些库以及使用它们的程序所需完成的步骤。

    首先在 pdir/d2 目录中构建 libx2.so。

    cd /home/mtk/pdir/d2
    gcc -g -c -fPIC -Wall modx2.c
    gcc -g -shared -o libx2.so modx2.o

    接着在 pdir/d1 目录中构建 libx1.so。由于 libx1.so 依赖于 libx2.so,并且 libx2.so 位于一个非标准目录中,因此在指定 libx2.so 的运行时位置时需要使用–rpath 链接器选项。这个选项的取值与库的链接时位置(由-L 选项指定)可以不同,尽管在这个例子中这两个位置是相同的。

    cd /home/mtk/pdir/d1
    gcc -g -c -fPIC -Wall modx1.c
    gcc -g -shared -o libx1.so modx1.o -Wl,-rpath,/home/mtk/pdir/d2 -L/home/mtk/pdir/d2 -lx2

    最后在 pdir 目录中构建主程序。 由于主程序使用了 libx1.so 并且这个库位于一个非标准目录中,因此还需要使用–rpath 链接器选项。

    cd /home/mtk/pdir
    gcc -g -Wall -o prog prog.c -Wl,-rpath,/home/mtk/pdir/d1 -L/home/mtk/pdir/d1 -lx1

    注意在链接主程序时无需指定 libx2.so。由于链接器能够分析libx1.so 中的 rpath 列表,因此它能够找libx2.so,同时在静态链接阶段解析出所有的符号。 使用下面的命令能够检查 prog 和 libx1.so 以便查看它们的 rpath 列表的内容。

    objdump -p prog |grep PATH
    objdump -p d1/libx1.so |grep PATH

    使用 ldd 命令能够列出 prog 的完整的动态依赖列表。

    ldd prog
    ELF DT_RPATH 和 DT_RUNPATH 条目

    在第一版 ELF 规范中,只有一种 rpath 列表能够被嵌入到可执行文件或共享库中,它对应于 ELF 文件中的 DT_RPATH 标签。后续的 ELF 规范舍弃了 DT_RPATH,同时引入了一种新标签 DT_RUNPATH 来表示 rpath 列表。

    这两种 rpath 列表之间的差别在于当动态链接器在运行时搜索共享库时它们相对于 LD_LIBRARY_PATH 环境变量的优先级: DT_RPATH 的优先级更高,而 DT_RUNPATH 的优先级则更低。 在默认情况下,链接器会将 rpath 列表创建为 DT_RPATH 标签。为了让链接器将 rpath 列表创建为DT_RUNPATH 条目必须要额外使用– –enable–new–dtags(启用新动态标签)链接器选项。如果使用这个选项重建程序并且使用 objdump 查看获得的可执行文件,那么将会看到下面这样的输出。

    gcc -g  -Wall -o prog prog.c -Wl,--enable-new-dtags
    -Wl,rpath,/home/mtk/pdir/d1 -L /home/mtk/pdir/d1 -lx1
    objdump -p prog |grep PATH

    从上面可以看出,可执行文件包含了 DT_RPATH 和 DT_RUNPATH 标签。链接器采用这种方式复写了 rpath 列表是为了让不理解 DT_RUNPATH 标签的老式动态链接器能够正常工作。。理解 DT_RUNPATH 标签的链接器会忽略DT_RPATH 标签。

    在 rpath 中使用$ORIGIN

    假设需要发布一个应用程序,这个应用程序使用了自身的共享库,但同时不希望强制要求用户将这些库安装在其中一个标准目录中,相反,需要允许用户将应用程序解压到任意异目录中,然后能够立即运行这个应用程序。这里存在的问题是应用程序无法确定存放共享库的位置,除非要求用户设置 LD_LIBRARY_PATH 或者要求用户运行某种能够标识出所需的目录的安装脚本,但这两种方法都不是令人满意的方法。 为解决这个问题,在构建链接器的时候增加了对 rpath 规范中特殊字符串$ORIGIN(或等价的${ORIGIN})的支持。动态链接器将这个字符串解释成“包含应用程序的目录”。这意味着可以使用下面的命令来构建应用程序。

    gcc -Wl,rpath,'$ORIGIN'/lib ...

    上面的命令假设在运行时应用程序的共享库位于包含应用程序的可执行文件的目录的子目录 lib 中。这样就能向用户提供一个简单的包含应用程序及相关的库的安装包,同时允许用户将这个包安装在任意位置并运行这个应用程序了(即所谓的“ turn-key 应用程序”)。

    41.11 在运行时找出共享库

    在解析库依赖时,动态链接器首先会检查各个依赖字符串以确定它是否包含斜线( /),因为在链接可执行文件时如果指定了一个显式的库路径名的话就会发生这种情况。如果找到了一个斜线,那么依赖字符串就会被解释成一个路径名(绝对路径名或相对路径名),并且会使用该路径名加载库。否则动态链接器会使用下面的规则来搜索共享库。 1. 如果可执行文件的 DT_RPATH 运行时库路径列表( rpath)中包含目录并且不包含DT_RUNPATH 列表,那么就搜索这些目录(按照链接程序时指定的目录顺序)。 2. 如果定义了 LD_LIBRARY_PATH 环境变量,那么就会轮流搜索该变量值中以冒号分隔的各个目录。如果可执行文件是一个 set-user-ID 或 set-group-ID 程序,那么就会忽略LD_LIBRARY_PATH 变量。这项安全措施是为了防止用户欺骗动态链接器让其加载一个与可执行文件所需的库的名称一样的私有库。 3. 如果可执行文件 DT_RUNPATH 运行时库路径列表中包含目录,那么就会搜索这些目录 4. 检查/etc/ld.so.cache 文件以确认它是否包含了与库相关的条目。 5. 搜索/lib 和/usr/lib 目录。

    41.12 运行时符号解析

    假设在多个地方定义了一个全局符号(即函数或变量),如在一个可执行文件和一个共享库中或在多个共享库中。那么如何解析指向这个符号的引用呢? 假设现在有一个主程序和一个共享库,它们两个都定义了一个全局函数 xyz(),并且共享库中的另一个函数调用了 xyz(),如图 41-5 所示。

    在构建共享库和可执行程序并运行这个程序之后能够看到下面的输出。

    gcc -g -c -fPIC -Wall -c foo.c
    gcc -g -shared  -o libfoo.so foo.o
    gcc -g -o prog prog.c libfoo.so
    LD_LIBRARY_PATH=. ./prog

    从上面输出的最后一行可以看出,主程序中的 xyz()定义覆盖(优先)了共享库中的定义。 尽管这种处理方式在一开始看起来有些令人惊讶,但这样做是有历史原因的。第一个共享库实现在设计时的目标是使符号解析的默认语义与那些和同一库等价的静态库进行链接的 应用程序中的符号解析的语义完成一致。这意味着下面的语义是正确的。 1.主程序中全局符号的定义覆盖库中相应的定义。 2.如果一个全局符号在多个库中进行了定义,那么对该符号的引用会被绑定到在扫描库时找到的第一个定义,其中扫描顺序是按照这些库在静态链接命令行中列出时从左至右的顺序。 虽然这些语义使得从静态库到共享库的转变变得相对简单了,但这种做法会导致一些问题。其中最大的问题是这些语义在使用共享库实现一个自包含的子系统时会与共享库模型产生矛盾。

    在默认情况下,共享库无法确保一个指向其自身的某个全局符号的引用会真正被绑定到该符号在库中的定义上,从而导致当该共享库被集成到一个更大的系统中时共享库的属性可能会发生改变。这会导致应用程序出现令人意料之外的行为,同时也使得分治调试的执行变得更加困难(即尝试使用更少或不同的共享库来重现问题)。 在上面的例子中,如果想要确保在共享库中对 xyz()的调用确实调用了库中定义的相应函数,那么在构建共享库的时候就需要使用–Bsymbolic 链接器选项。

    gcc -g -c -fPIC -Wall -c foo.c
    gcc -g -shared -Wl,-Bsymbolic  -o libfoo.so foo.o
    gcc -g -o prog prog.c libfoo.so
    LD_LIBRARY_PATH=. ./prog

    –Bsymbolic 链接器选项指定了共享库中对全局符号的引用应该优先被绑定到库中的相应定义上(如果存在的话)。 (注意不管是否使用了这个选项,在主程序中调用 xyz()总是会调用 主程序中定义的 xyz()。

    41.13 使用静态库取代共享库

    虽然在大多数情况下都应该使用共享库,但在某些场景中静态库则更加适合。特别地,静态链接的应用程序包含了它在运行时所需的全局代码这一事实是非常有利的。如当用户不希望或者无法在运行程序的系统上安装共享库或者程序在另一个无法使用共享库的环境中运行时,静态链接就派上用场了。

    此外,即使是一个兼容的共享库升级也可能会在无意中引入一个 Bug,从而导致应用程序无法正常工作。通过静态链接应用程序就能确保系统上共享库的变动不会影响到它并且它已经拥有了运行所需的全局代码。 在默认情况下,当链接器能够选择名称一样的共享库和静态库时(如在链接时使用 –Lsomedir –ldemo 并且 libdemo.so 和 libdemo.a 都存在)会优先使用共享库。

    要强制使用库的静态版本则可以完成下列之一。 1.在 gcc 命令行中指定静态库的路径名(包括.a 扩展)。 2.在 gcc 命令行中指定-static 选项。 3.使用–Wl,–Bstatic 和–Wl,–Bdynamic gcc 选项来显式地指定链接器选择共享库还是静态库。在 gcc 命令行中可以使用-l 选项来混合这些选项。链接器会按照选项被指定时的顺序来处理这些选项。

  • 相关阅读:
    聊聊oracle rowid与索引结构
    去除文本中的空行
    表与表空间
    文件导入到数据库——复杂版
    文件导入到数据库
    vmware workstation 12+oracle linux 5.10+oracle 11g 搭建rac
    oracle判断是否实施了rac
    面向对象-小练习
    面向对象-面向对象的可拓展性
    面向对象-一切皆对象
  • 原文地址:https://www.cnblogs.com/wangbin2188/p/14658027.html
Copyright © 2011-2022 走看看