【问题标题】:Selective Static Linking Of Library Functions In Shared Library共享库中库函数的选择性静态链接
【发布时间】:2010-12-23 07:16:51
【问题描述】:

我想创建一个使用来自 3rd 方静态库的函数的共享库。例如,foobar 来自 libfoobar.a。我知道我的主应用程序也在使用 foo 并将导出该符号。所以我只想链接bar 以节省代码大小并使'foo'未解决(因为它将由主应用程序提供)。如果我包含libfoobar.a,链接器ld 将在我的共享库中包含这两个函数。如果我不包含libfoobar.a,我的库将无法访问函数bar,因为应用程序本身没有链接到bar。问题:

  • 有没有办法告诉 ld 在构建共享库时只解析某些符号?
  • libfoobar.a 变成共享库?
  • libfoobar.a 中提取包含函数bar 的文件并在链接器行上指定它?
  • 别担心,运行时加载器会使用您应用程序中的bar,所以共享库中bar 的副本不会被加载?

【问题讨论】:

    标签: linux shared-libraries ld static-linking


    【解决方案1】:

    我不是共享库方面最大的专家,所以我在这里可能错了!

    如果我猜对了您要执行的操作,只需将您的共享库与 libc.so 链接即可。您不希望在库中嵌入额外的 sscanf 副本。

    如果您对答案感兴趣,我在弄清楚您的意思之前就回答了您的问题。

    有没有办法告诉 ld 在构建共享库时只解析某些符号?

    共享库的符号表中只有外部函数和变量,而不是静态函数和变量。

    当您构建共享库时,链接器命令行上的对象中未找到的任何符号都将保持未解析状态。如果链接器抱怨,您可能需要将您的共享库链接到 shared libc。您可以拥有依赖于其他共享库的共享库,而 ld.so 可以处理依赖链。

    如果我有更多的代表,我会问这个作为评论: 您是否有自定义版本的 sprintf/sscanf,或者您的共享库可以使用 -lc 中的实现吗?如果 -lc 没问题,那么我的回答可能会解决您的问题。如果没有,那么您需要使用仅具有您需要的功能的对象来构建您的共享库。即不要将其链接到 /usr/lib/libc.a。

    也许我被你弄糊涂了

    libc.a(实际上不是“真正的”libc) 线。 /usr/lib/libc.a 真的是 glibc(在 linux 上)。它是 libc.so 中相同代码的静态链接副本。除非你说的是你自己的 libc.a(我一开始就是这么想的)......

    将 libc.a 变成共享库? 您可能可以,但不要这样做,因为它可能没有编译为与位置无关的代码,因此 ld.so 在运行时需要进行大量重定位。

    从 libc.a 中提取 sscanf 并在链接器行上指定?

    有可能。 ar t /usr/lib/libc.a 列出内容。 (ar 的 args 类似于 tar。tar 是用于磁带的 ar....这里是老式 Unix。)可能没那么容易,因为 sscanf 可能依赖于 .a 中其他 .o 文件中的符号。

    【讨论】:

    • 对 libc 的混淆感到抱歉。我只是指任何第 3 方静态库,并以 libc 为例。我将修改我的问题以澄清这一点。
    【解决方案2】:

    回答您修改后的更清晰的问题。

    请记住,通常共享库的意义在于多个程序可以链接到它。因此,仅当主程序始终提供该符号(通过静态库或其他方式)时,您对使用主程序符号的函数的优化才有效。这通常不是人们想要做的。

    如果它只是几个小功能,也许你应该让它成为。您最终可能会得到两份函数代码副本,一份在您的 shlib 中,一份在主程序中。如果它们很小(或至少不是很大),或者不经常调用并且对性能不重要,那么拥有两个副本的代码大小/ I-cache 命中就不用担心了。 (翻译:我不知道如何避免它,所以我可能不会花时间去查找它并制作一个更复杂的 Makefile 来避免它。)

    有关一些 cmets 的其他答案,请参阅我关于使用 ar 从静态库中提取内容的其他答案。摘要:可能很重要,因为您不知道 .a 中各种 .o 文件之间的依赖关系。

    通过让您的共享库导出它从静态库中提取的符号,您可能会做您希望的事情。然后,当您链接主应用程序时,将您的共享库放在链接器命令行上的静态库之前。 ld 将在您的 shlib 中找到“foo”,并使用该副本(如果可以使用此重新导出技巧),但对于“bar”,它必须包含来自静态库的副本。

    ld --export-dynamic 可能是您导出动态符号表中所有符号所需的。试试看。并在文档/手册页中搜索“导出”。 “export”是使符号在库中可见的术语。 --export-all-symbols 在 i386 PE (windows DLL) 部分,否则它可能会成功。

    【讨论】:

    • 注意到 ld 手册页中的某些内容:--just-symbols=filename: "从文件名中读取符号名称及其地址,但不要重新定位它或将其包含在输出中。这允许您的输出文件以符号方式引用其他程序中定义的内存的绝对位置。”
    • 由于共享库是一个“插件”(即并不总是被加载),它不能为其他代码尤其是主应用程序提供符号。最简单的大概就是把 3rd 方静态库变成动态库了。
    【解决方案3】:

    以下几点试图回答我提出的问题:

    • ld 似乎不允许您省略静态库中某些符号的链接。使用--just-symbols--undefined(或EXTERN 链接器脚本命令)不会阻止ld 链接符号。
    • 将静态库 libfoobar.a 转换为共享库 libfoobar.so.1.0,并导出所有可见符号。您还可以使用--version-script 和其他方法仅导出符号的子集。

      ld -shared -soname libfoobar.so.1 -o libfoobar.so.1.0 --whole-archive libfoobar.a --no-whole-archive

    • 最好从静态库的副本删除存档成员而不是提取它们,因为您可能需要管理内部依赖项.例如,假设您要导出所有符号,您可以从主可执行文件生成映射文件。然后,您可以对可执行文件从静态库副本中提取的所有存档成员进行 grep,并将它们从副本中删除。所以当你的 DSO 在静态库中链接时,它会留下相同的符号未解析。

    • 如果您使用--pie 选项编译可执行文件,则可以将主可执行文件指定为 DSO 的共享库。如果 DSO 在链接命令中位于静态库之前,您的 DSO 将首先链接到您的可执行文件。需要注意的是,主可执行文件必须通过LD_LIBRARY_PATH-rpath 提供。此外,使用 strace 表明,由于可执行文件是库的依赖项,因此在 DSO 加载时会再次加载它。

      ld -shared -rpath '$ORIGIN' -L. -lc -ldl -o DSO.so DSO.o app libfoobar.a

    • 动态链接器将首先使用可执行文件的 foo 版本,除非您使用 RTLD_DEEPBIND 标志调用 dlopen()。使用 strace 揭示了整个 DSO 是文件映射到内存中的 mmap2()。然而,维基百科声称,对于 mmap,“在访问特定位置后,从磁盘的实际读取是以“惰性”方式执行的。”如果这是真的,那么重复的 foo 将不会被加载。请注意,仅当您的 DSO 导出函数 foo 时才会发生覆盖。否则,每当您的 DSO 调用 foo 时,将使用静态链接到 DSO 的函数 foo

    总之,如果 mmap() 使用惰性读取,那么最好的解决方案是以正常方式链接您的 DSO,让动态链接器和 linux 处理其余的工作。 p>

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-07-29
      相关资源
      最近更新 更多