为什么ELF中其他共享库的函数长度信息?

min*_*ros 18 c++ linux powerpc elf

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

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

但是,有一种情况只是delete添加到库libTableManager.so的类TableManager(在TableManager.cpp文件中!)的析构函数中,还有库libB.so的二进制/校验和(它使用类TableManager)已经改变了.

TableManager.h:

class TableManager 
{
public:
    TableManager();
    ~TableManager();
private:
    int* myPtr;
}
Run Code Online (Sandbox Code Playgroud)

TableManager.cpp:

TableManager::~TableManager()
{
    doSomeCleanup();
    delete myPtr;     // this delete has been added
}
Run Code Online (Sandbox Code Playgroud)

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

527: 00000000 668 FUNC GLOBAL DEFAULT UND _ZN12TableManagerD1Ev

所以我的问题是:

  1. 为什么函数的长度实际存储在客户端lib中?起始地址不足够吗?
  2. 在编译/链接libB.so(某种"剥离")时,这可以以某种方式被抑制吗?我们真的希望减少这种依赖程度......

min*_*ros 11

答对了.它实际上是binutils中的一个"bug",它们在2008年发现并修复过.大小信息实际上没用!

Simon Baldwin 在binutils邮件列表中的内容完全描述了问题(由我强调):

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

我们遇到了旧系统的问题(binutils 2.16).我将它与桌面系统上的2.20版进行了比较,并且 - vo - 共享全局符号的长度为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_
Run Code Online (Sandbox Code Playgroud)

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

在此输入图像描述

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

  • 你能接受自己的答案(这是正确的),所以它出现在错误的答案之上吗? (2认同)

Jer*_*fin 8

您需要仔细阅读加载程序的代码,但我认为在这种情况下,我们可以对该长度字段的目标进行相当合理的猜测.

加载器需要获取将要放入进程的所有函数,并将它们映射到内存地址.因此,它为第一个函数提供了一个地址.然后,第二个在第一个结束之后 - 但要知道"第一个结束",它需要知道第一个函数有多长.

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

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

编辑:由于有些人显然错过了"意图完成"的(显然太过)微妙的含义,让我完全清楚:我有理由相信这个领域不是(而且从来没有)实际使用过.

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

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

然而,这并没有改变该领域仍然存在的事实,也没有改变OP询问其存在的原因.简单地说"它没有被使用",虽然本身就是真的,但是/它没有回答他提出的问题.