【问题标题】:Change Linux shared library (.so file) version after it was compiled编译后更改 Linux 共享库(.so 文件)版本
【发布时间】:2016-09-27 23:35:40
【问题描述】:

我正在编译 Linux 库(对于 Android,使用 NDK 的 g++,但我敢打赌我的问题对任何 Linux 系统都有意义)。在将这些库交付给合作伙伴时,我需要用版本号标记它们。 我还必须能够以编程方式访问版本号(例如在“关于”对话框或GetVersion 函数中显示它)。

我首先使用未版本化标志(版本0.0)编译库,并且需要在完成测试后将此版本更改为真实版本,然后再将其发送给合作伙伴。我知道修改源代码并重新编译会更容易,但我们不想这样做(因为如果重新编译代码,我们应该再次测试所有内容,我们觉得它更不容易出错,请参阅 cmets发布,最后因为我们的开发环境是这样工作的:我们为 Windows 二进制文件执行此过程:我们设置了一个 0.0 资源版本字符串 (.rc),然后我们使用 verpatch 更改它...我们想在发布 Linux 二进制文件时使用相同类型的进程)。

这里最好的策略是什么? 总而言之,要求是:

  1. 使用“未设置”版本(0.0 或其他任何内容)编译二进制文件
  2. 能够将此“未设置”版本修改为特定版本,而无需重新编译二进制文件(理想情况下,运行第 3 方工具命令,就像我们在 Windows 下使用 verpatch 一样)
  3. 能够让库代码在运行时检索其版本信息

如果您的答案是“重命名.so”,那么请提供解决方案3.:如何在运行时检索版本名称(即:文件名)。

我正在考虑一些解决方案,但不知道它们是否可行以及如何实现它们。

  • 代码中有一个版本变量(一个string 或3 个int),并且以后有办法在二进制文件中更改它吗?使用二进制 sed...?
  • 资源中有一个版本变量,并且以后可以在二进制文件中更改它吗? (就像我们对 win32/win64 所做的那样)
  • 使用专用于此的 .so 字段(如 SONAME),并拥有允许更改它的工具...并使其可从 C++ 代码访问。
  • 重命名 lib + 更改 SONAME(没有找到如何实现)...并找到从 C++ 代码中检索它的方法。
  • ...

请注意,我们使用 QtCreator 来编译 Android .so 文件,但它们可能不依赖于 Qt。所以使用 Qt 资源并不是一个理想的解决方案。

【问题讨论】:

  • 您担心使用交换的一个版本字符串重新编译完全相同的代码可能会破坏事情并需要进行新的测试,但是对于涉及破解自己的脚本将更改的字符串修补到二进制?这……很有趣。
  • 您的发言完全有道理。实际上我们曾经这样做过:更改代码中的版本号(例如更改为 3.2)并重新编译。我们是一个小型结构,人们同时进行开发和集成......然后,这样做的开发人员经常忘记将版本字符串重置为 0.0,然后当它们绝对不是 3.2 版本时,您会得到标记为 3.2 的开发二进制文件......那就是为什么在编译后标记二进制文件对我们来说似乎更安全(这就是我们最终在 Windows 下工作的方式)。
  • 我的评论仍然适用 :-) 为什么不从根本上解决您的问题,并更改您的构建环境以将任何调试构建的版本号更改回 0.0?最简单的方法是使用版本号的预处理器定义。该解决方案的巧妙之处在于它可以很容易地被采用来包括例如调试版本的版本字符串中的修订 ID。
  • 您的问题不准确,更改 SONAME 不需要(重新)编译,只需(重新)链接已编译的对象。因此,您无需更改任何源代码即可更改 SONAME 并使用正确的 SONAME 重新创建共享库。
  • 是的,这是非常糟糕的设计,没有提供删除版本信息的方法。

标签: c++ linux g++ shared-libraries versioning


【解决方案1】:

恐怕你从头开始解决你的问题。首先 SONAME 是在链接时作为链接器的参数提供的,所以一开始您需要找到一种从源代码获取版本并传递给链接器的方法。一种可能的解决方案 - 使用 ident 实用程序并在您的二进制文件中提供版本字符串,例如:

const char version[] = "$Revision:1.2$"

这个字符串应该以二进制形式出现,ident 实用程序会检测到它。或者您可以直接使用grep 或类似的东西来解析源文件。如果有可能发生冲突,请放置额外的标记,您可以稍后使用该标记来检测此字符串,例如:

const char version[] = "VERSION_1.2_VERSION"

因此,您可以从源文件或 .o 文件中检测版本号,然后将其传递给链接器。这应该可以。

至于调试版本有 0.0 版本很容易 - 只需在构建调试时避免检测并无条件使用 0.0 作为版本。

对于第 3 方构建系统,我建议使用 cmake,但这只是我个人的偏好。解决方案也可以在标准 Makefile 中轻松实现。不过我不确定qmake

【讨论】:

  • 您的提案要求在编译时在代码中指定版本号。这并不能准确回答我的问题,该问题要求直接在已生成的 .so 文件中更改版本号。但也许我所要求的在 Linux 下是不可能做到的(而在 Windows 下是可行的)......我会等待赏金期结束,看看我是否能得到更好的东西。感谢您的帮助。
  • 首先您要求在不重新编译的情况下更改 SONAME,我的回答描述了如何做到这一点。您需要更清楚自己的要求。
  • 我不明白。当您建议“获取源版本”时,我如何在不编译的情况下执行您的建议。我的目标是编译版本为“0.0”的 .so 文件,然后在不重新编译/链接(使用任何外部工具)的情况下将其更改为其他文件。
  • 你不是说不重新链接,你说的是不重新编译。现在您正在动态添加需求。正如我所说,您需要更清楚地了解要求。
  • 在我看来编译/链接是相同的操作。就像我在 Windows 下使用 verpatch 一样。我的输入是一个 .so 文件,我希望将其中的字符串(或其他)从“0.0”更改为“3.2”......
【解决方案2】:

与 Slava 的讨论让我意识到任何const char* 在二进制文件中实际上都是可见的,然后可以轻松地修补到其他任何东西。

所以这是解决我自己问题的好方法:

  1. 创建一个库:
    • const char version[] = "VERSIONSTRING:00000.00000.00000.00000"; 的定义(我们需要它足够长的时间,因为我们以后可以安全地修改二进制文件内容但不能扩展它...)
    • 一个GetVersion 函数,它将清除上面的version 变量(删除VERSIONSTRING: 和无用的0)。它会返回:
      • 0.0 如果versionVERSIONSTRING:00000.00000.00000.00000
      • 2.3 如果 versionVERSIONSTRING:00002.00003.00000.00000
      • 2.3.40 如果versionVERSIONSTRING:00002.00003.00040.00000
      • ...
  2. 编译库,我们命名为mylib.so
  3. 从程序中加载它,询问它的版本(调用GetVersion),它返回0.0,不足为奇
  4. 创建一个小程序(用 C++ 完成,但可以用 Python 或任何其他语言完成),它将:
    • 在内存中加载整个二进制文件内容(使用std::fstreamstd::ios_base::binary
    • 在里面找到VERSIONSTRING:00000.00000.00000.00000
    • 确认它只出现一次(以确保我们不会修改我们无意修改的内容,这就是为什么我在字符串前面加上VERSIONSTRING,以使其更加统一......)
    • 如果预期的二进制数是2.3.40,则将其修补到VERSIONSTRING:00002.00003.00040.00000
    • 从修补的内容中保存二进制文件
  5. 使用上述工具修补mylib.so(例如请求版本2.3
  6. 运行与第 3 步相同的程序,它现在报告 2.3!

没有重新编译也没有链接,你修补了二进制版本!

【讨论】:

  • 您不必将整个文件加载到内存中然后覆盖整个文件。您可以使用strings 来查找您的字符串,它会告诉您文件偏移量,然后打开该文件进行写入、定位和覆盖您的字符串。
  • @Slava:当然。我试着快速编写代码来验证这个概念,它绝对可以优化。加载整个文件可以很容易地检查字符串只出现一次。
猜你喜欢
  • 1970-01-01
  • 2014-02-22
  • 1970-01-01
  • 1970-01-01
  • 2018-10-07
  • 1970-01-01
  • 2020-12-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多