编译后如何更改Linux共享库(.so文件)的版本号?

17

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

我首先使用未带版本号的标志(版本0.0)编译库,在将其发送给合作伙伴之前需要将此版本更改为真实版本。我知道修改源代码并重新编译会更容易,但我们不想这样做(因为如果重新编译代码,我们应该再次测试一切,我们觉得这样会少出错,请参见本帖子的注释,最后是因为我们的开发环境是这样工作的:对于Windows二进制文件,我们设置一个0.0资源版本字符串(.rc),然后我们稍后使用verpatch来更改它...我们希望在发货Linux二进制文件时采用同样的过程)。

在这里,什么是最好的策略?总结要求如下:

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

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

我想到了一些方法,但不知道它们是否可行以及如何实现它们。

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

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


2
你害怕重新编译完全相同的代码并更换一个版本字符串可能会破坏事情并需要进行新的测试,但对于涉及自己编写脚本将更改的字符串打补丁到二进制文件的解决方案却感到完全没问题?那很有趣。 - Phillip
你的评论非常有道理。实际上,我们过去常常这样做:在代码中更改版本号(例如更改为3.2),然后重新编译。我们是一个小型组织,人员既负责开发又负责集成...因此,开发人员经常忘记将版本字符串重置为0.0,然后稍后您会得到标记为3.2的开发二进制文件,但它们绝对不是3.2发布版...这就是为什么在编译后一次标记二进制文件对我们来说更安全的原因(这也是我们最终在Windows下工作的方式)。 - jpo38
我的评论仍然适用 :-) 为什么不从根本上解决问题,改变您的构建环境,将版本号更改回任何调试构建的0.0呢?最简单的方法是使用预处理器定义版本号。这种解决方案的好处是可以轻松地采用,例如在调试构建的版本字符串中包含修订ID。 - Phillip
你的问题不准确,改变SONAME不需要(重新)编译,只需要(重新)链接已经编译好的对象。因此,你不需要改变任何源代码来改变SONAME并重新创建一个带有正确SONAME的共享库。 - Slava
是的,这确实是一个糟糕的设计,没有提供删除版本信息的方法。 - Jiang YD
2个回答

2
与Slava的讨论使我意识到,任何const char*实际上都可以在二进制文件中显示,因此可以轻松地将其修补为其他任何内容。
因此,以下是解决我的问题的好方法:
  1. 创建一个库,其中包括:
    • const char version[] = "VERSIONSTRING:00000.00000.00000.00000";的定义(我们需要足够长,因为稍后可以安全地修改二进制文件内容,但不能扩展它...)
    • 一个GetVersion函数,用于清除上面的version变量(删除VERSIONSTRING:和无用的0)。它将返回:
      • 如果versionVERSIONSTRING:00000.00000.00000.00000,则为0.0
      • 如果versionVERSIONSTRING:00002.00003.00000.00000,则为2.3
      • 如果versionVERSIONSTRING:00002.00003.00040.00000,则为2.3.40
      • ...
  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. 使用上述工具(例如请求版本2.3)修补mylib.so
  6. 运行与步骤3相同的程序,它现在报告2.3
无需重新编译或链接,您已经修补了二进制版本!

你不必将整个文件加载到内存中,然后覆盖整个文件。你可以使用 strings 查找你的字符串,它会告诉你文件偏移量,然后只需打开该文件进行写入,定位并覆盖你的字符串即可。 - Slava
@Slava:当然。我尝试快速编写代码以验证概念,它绝对可以优化。加载整个文件使得检查字符串是否仅出现一次变得容易。 - jpo38

2

我担心您从问题的末尾开始解决。首先,SONAME是由链接器在链接时提供的参数,因此一开始您需要找到一种方法从源代码中获取版本并传递给链接器。其中一个可能的解决方案是使用ident实用程序,在您的二进制文件中提供版本字符串,例如:

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

这个字符串应该以二进制形式出现,ident工具将检测到它。或者您可以直接使用grep或类似的东西解析源文件。如果存在冲突的可能性,请添加其他标记,以便稍后可以使用它来检测此字符串,例如:

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

所以,您可以从源文件或.o文件中检测版本号,并将其传递给链接器。这应该可行。
至于调试版本需要具有版本0.0,很容易 - 在构建调试时避免检测并无条件地使用0.0作为版本即可。
对于第三方构建系统,我建议使用cmake,但这只是我的个人偏好。也可以在标准的Makefile中轻松实现解决方案。不过,我不确定qmake是否可以实现此解决方案。

您的提案要求在编译时在代码中指定版本号。这并没有准确回答我的问题,我要求的是一种在已生成的.so文件内直接更改版本号的方法。但也许在Linux下无法实现(而在Windows下可以)...我会等待奖励期结束,看是否有更好的解决方案。感谢您的帮助。 - jpo38
我不明白。当你建议“获取源代码版本”时,我该如何在不编译的情况下完成你的建议。我的目标是使用版本“0.0”编译.so文件,然后使用任何外部工具在不重新编译/链接的情况下将其更改为其他内容。 - jpo38
1
在我看来,编译和链接是相同的操作。就像我在Windows下使用verpatch一样。我的输入是一个.so文件,我希望将其中的一个字符串(或其他内容)从“0.0”更改为“3.2”。 - jpo38
我的原始问题并不是要更改SONAME,使用SONAME来存储版本只是一个建议,而不是要求。现在我意识到通过您的帖子,字符串可以从二进制文件中看到,为什么不直接使用二进制编辑器将“0.0”替换为“3.2”,这样简单地工作不就行了吗? - jpo38
这取决于你所说的“work”的含义。如果你的函数返回该字符串,那么如果你在二进制中更改它,它将返回不同的字符串。但是客户端的链接器将在检查兼容性时验证SONAME。因此从这个意义上说,它不会起作用。如果你不关心链接器,只需要更改二进制字符串,那么是的,你可以编译它并稍后修改它(但请确保你有足够的空间来扩展字符串,如果必要的话)。 - Slava
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接