18

我们的项目(C++、Linux、gcc、PowerPC)由几个共享库组成。在发布新版本的软件包时,只有那些库应该更改其源代码实际受到影响的库。“更改”是指绝对二进制身份(比较文件的校验和。不同的校验和 - > 根据策略的不同版本)。(我应该提一下,整个项目总是同时构建的,无论每个库是否更改了任何代码)。

通常这可以通过隐藏包含的头文件的私有部分而不更改公共部分来实现。

但是,有一种情况是delete,库 libTableManager.so 的类 TableManager(在 TableManager.cpp 文件中!)的析构函数中添加了一个,而库 libB.so(使用类 TableManager)的二进制/校验和了。

表管理器.h:

class TableManager 
{
public:
    TableManager();
    ~TableManager();
private:
    int* myPtr;
}

表管理器.cpp:

TableManager::~TableManager()
{
    doSomeCleanup();
    delete myPtr;     // this delete has been added
}

通过检查 libB.so 并readelf --all libB.so查看 .dynsym 部分,结果发现所有函数的长度,甚至是来自其他库的动态使用的函数,都存储在 libB 中!它看起来像这样(长度是第 3 列中的 668):

527: 00000000 668 FUNC GLOBAL DEFAULT UND _ZN12TableManagerD1Ev

所以我的问题是:

  1. 为什么函数的长度实际上存储在客户端库中?起始地址还不够吗?
  2. 在编译/链接 libB.so (某种“剥离”)时可以以某种方式抑制这种情况吗?我们真的很想减少这种依赖程度......
4

2 回答 2

11

答对了。这实际上是他们在 2008 年发现并修复的 binutils 中的一种“错误”。大小信息实际上没有用

Simon Baldwin在 binutils 邮件列表中所写的内容准确地描述了这个问题(我强调):

目前,未定义的 ELF 符号的大小在链接时从提供符号的目标文件或 DSO 中复制出来。 这个大小是不可靠的,例如在两个 DSO 的情况下,一个链接到另一个。较低级别的 DSO 可以进行更改符号大小的 ABI 保留更改,而无需重新构建较高级别的 DSO。如果重新构建更高级别的 DSO,监控文件校验和的工具将记录由于未定义符号的大小更改而发生的更改,即使有关更高级别 DSO 的其他任何内容都没有更改。这可能导致 基于校验和的系统中不必要的和不受欢迎的重建和更改级联。

我们遇到了旧系统(binutils 2.16)的问题。我将它与桌面系统上的 2.20 版本进行了比较,然后 - 瞧 - 共享全局符号的长度为 0:

157: 00000000     0 FUNC    GLOBAL DEFAULT  UND _ZN12TableManagerD1Ev
158: 00000000     0 FUNC    GLOBAL DEFAULT  UND _ZNSs6assignERKSs@GLIBCXX_3.4 (2)
159: 00000000     0 FUNC    GLOBAL DEFAULT  UND sleep@GLIBC_2.0 (6)
160: 00000000     0 FUNC    GLOBAL DEFAULT  UND _ZN4Gpio11setErrorLEDENS_

所以我比较了两个 binutils 源代码,并且 - 再次瞧瞧 - 有 Alan 在邮件列表中建议的修复:

在此处输入图像描述

也许我们只是应用补丁并重新编译 binutils,因为我们需要使用较旧的平台。谢谢你的耐心。

于 2012-11-30T14:27:47.370 回答
8

您需要通读加载程序的代码才能确定,但​​我认为在这种情况下,我们可以对长度字段的目的做出相当合理的猜测。

加载器需要获取所有将要放入进程的函数,并将它们映射到内存地址。所以,它给了第一个函数一个地址。然后,第二个出现在第一个结束之后——但要知道“第一个结束”,它需要知道第一个函数有多长。

我可以看到两种方法来获取该长度:它可以将其编码在文件中(正如您在 ELF 中看到的那样),或者它可以打开包含该函数的文件,并从那里。

后者似乎(在我看来)有两个相当明显的缺点。首先是速度——打开所有这些额外的文件,解析它们的头文件等,只是为了获取函数的长度几乎肯定比从当前文件中为每个函数读取额外的四个字节要慢。第二个是方便:只要您不调用文件中的任何函数,您根本不需要该文件存在。如果您直接从文件中读取长度(例如,像 Windows 通常对 DLL 所做的那样),您需要该文件存在于目标系统上,即使它从未实际使用过。

编辑:由于有些人显然错过了(显然也是)“打算完成”的微妙含义,让我完全清楚:我有理由确定这个字段没有(也从未被)实际使用过。

然而,任何认为这个答案是错误的人都需要回到编程 101 并了解接口和实现之间的区别。

在这种情况下,文件格式定义了一个接口——加载程序可以使用的一组功能。在 Linux 的特定情况下,似乎从未使用过此字段。

然而,这并没有改变该领域仍然存在的事实,也没有改变 OP 询问它为什么存在的事实。简单地说“它没有被使用”,虽然它本身是正确的,但会/不会回答他提出的问题。

于 2012-11-29T15:03:45.637 回答