【问题标题】:C++ binary identification (manifest)C++ 二进制标识(清单)
【发布时间】:2012-09-15 16:58:43
【问题描述】:

我们有大量 C++ 项目(GCC、Linux,主要是静态库),它们之间存在许多依赖关系。然后我们使用这些库编译一个可执行文件并在前端部署二进制文件。能够识别该二进制文件将非常有用。理想情况下,我们希望有一个小脚本,可以直接从二进制文件中检索以下信息:

$ident binary
$binary : Product=PRODUCT_NAME;Version=0.0.1;Build=xxx;User=xxx...
$  dependency: Product=PRODUCT_NAME1;Version=0.1.1;Build=xxx;User=xxx...
$  dependency: Product=PRODUCT_NAME2;Version=1.0.1;Build=xxx;User=xxx...

因此它应该显示二进制文件本身及其所有依赖项的所有信息。

目前我们的做法是:

  1. 在编译每个产品的过程中,我们生成 Manifest.h 和 Manifest.cpp,然后将 Manifest.o 注入二进制文件

  2. ident 脚本解析目标二进制文件,在那里找到生成的内容并打印此信息

但是,对于不同版本的 gcc,这种方法并不总是可靠的。 我想问问 SO 社区 - 有没有更好的方法来解决这个问题?

感谢您的建议

【问题讨论】:

  • 我们正在做类似的事情,但是我们添加了一些 API 以允许在中心位置注册版本信息,这样我们不仅可以通过 ident 检索它,还可以通过一些 API 调用来显示它在应用程序本身。所以,总的来说,我会说你的方法是合理的;)你观察到的确切问题是什么?
  • @Andreas。谢谢。问题只发生在一个平台上(Linux x86,gcc 4.1.2) - 由于某种原因,编译的二进制清单不存在(可能因为没有引用或一些棘手的修改而优化)。我们对此有一个解决方法(我们使用古老的编译器编译 Manifest.o),但我感觉我们做了一些 hack。
  • 您能否只添加一个命令行参数,使可执行文件将版本信息转储到标准输出以供脚本抓取?
  • @JonathanPotter:谢谢,但这是如何将版本信息(包括所有部门)放入可执行文件的主要问题。你建议如何处理这个问题?是的,“命令行方法”简化了信息的输出,但仅适用于可执行文件,并且需要将相同的代码插入到每个可执行文件中,这更具侵入性
  • 可能链接的依赖项是已知的并且不会经常更改,因此只需让每个依赖项导出一个提供其版本号的函数,并让 exe 调用所有这些并将它们串在一起.

标签: c++ gcc binary manifest identity


【解决方案1】:

在源代码(您的Manifest.h.cpp)中存储数据的一个问题是文字数据的大小限制,这取决于编译器。

我的建议是使用ld。它允许您在 ELF 文件中存储任意二进制数据(objcopy 也是如此)。如果您更喜欢编写自己的解决方案,请查看libbfd

假设我们有一个hello.cpp,其中包含通常的 C++“Hello world”示例。现在我们有以下make文件(GNUmakefile):

hello: hello.o hello.om
    $(LINK.cpp) $^ $(LOADLIBES) $(LDLIBS) -o $@

%.om: %.manifest
    ld -b binary -o $@ $<

%.manifest:
    echo "$@" > $@

我在这里做的是分离出链接阶段,因为我希望清单(在转换为 ELF 对象格式之后)也链接到二进制文件中。由于我使用的是后缀规则,这是一种方法,其他方法当然是可能的,包括更好的清单命名方案,它们也最终成为 .o 文件,GNU make 可以弄清楚如何创建它们。在这里,我要明确说明食谱。所以我们有.om 文件,它们是从.manifest 文件创建的清单(任意二进制数据)。配方规定将二进制输入转换为 ELF 对象。创建.manifest 本身的方法只是将字符串通过管道传输到文件中。

显然,在您的案例中,棘手的部分不是存储清单数据,而是生成它。坦率地说,我对你的构建系统知之甚少,甚至无法尝试为.manifest 一代推荐一个配方。

无论您放入.manifest 文件中的任何内容都应该是一些结构化文本,可以由您提到的脚本解释,或者如果您实现命令行开关,甚至可以由二进制文件本身输出(并且忽略.so文件和.so 文件在从 shell 运行时被黑客入侵,使其表现得像普通的可执行文件)。

上面的 make 文件没有考虑依赖关系——或者说它不能帮助你以任何方式创建依赖关系列表。如果您清楚地为每个目标(即静态库等)表达您的依赖关系,您可能会强迫 GNU make 帮助您。但走那条路可能不值得……

另请看:


如果您想要从数据(在您的情况下为清单)生成的符号的特定名称,您需要使用稍微不同的路线并使用 John Ripley here 描述的方法。

如何访问符号?简单。将它们声明为外部(C 链接!)数据,然后使用它们:

#include <cstdio>

extern "C" char _binary_hello_manifest_start;
extern "C" char _binary_hello_manifest_end;

int main(int argc, char** argv)
{
        const ptrdiff_t len = &_binary_hello_manifest_end - &_binary_hello_manifest_start;
        printf("Hello world: %*s\n", (int)len, &_binary_hello_manifest_start);
}

符号是确切的字符/字节。您也可以将它们声明为char[],但这会导致以后出现问题。例如。用于printf 电话。

我自己计算大小的原因是因为 a.) 我不知道缓冲区是否保证为零终止 b.) 我没有找到任何有关与 *_size 变量接口的文档.

旁注:格式字符串中的* 告诉printf 它应该从参数中读取字符串的长度,然后选择下一个参数作为要打印的字符串。

【讨论】:

  • 非常感谢您的回答。我会调查并尝试这种方法,看起来很有希望。生成清单不是问题,因为我们已经有了收集所有信息的通用 makefile
  • 我按照你的建议生成了可执行文件,我可以通过 objdump 看到 _binary_src_manifest_start 和 _binary_src_manifest_end 存在,但是如何从中检索字符串数据?
  • @nogard:没有看到你的第二个问题,抱歉。现在就回答。
【解决方案2】:

您可以将任何您喜欢的数据插入到输出二进制文件的.comment 部分。您可以事后使用链接器执行此操作,但将其放置在您的 C++ 代码中可能更容易,如下所示:

 asm  (".section .comment.manifest\n\t"
       ".string \"hello, this is a comment\"\n\t"
       ".section .text");

 int main() {
   ....

在这种情况下,asm 语句应该任何函数之外。只要您的编译器将普通函数放在.text 部分中,这应该可以工作。如果不是,那么您应该进行明显的替换。

链接器应将所有 .comment.manifest 部分收集到最终二进制文件中的一个 blob 中。您可以从任何.o 或可执行文件中提取它们:

objdump -j .comment.manfest -s example.o

【讨论】:

  • 谢谢,非常有趣的方法,我会评估它!
  • 我做了非常简单的测试,它适用于可执行文件本身,但它似乎没有从静态库中收集清单(我可以在静态库对象文件中看到 .comment.manifest,但这个信息是最终可执行文件中不存在)。是否有一些特殊的链接器选项?
  • 嗯,我认为它会这样做,但我发现它没有使用带有 .comment 部分的通配符。您可以在没有.manifest 后缀的情况下再试一次,但它会与编译器提供的 cmets 混淆。或者您可以将自己的行添加到链接器脚本中,例如 .comment.manifest 0 : { *(.comment.manifest) }
  • 没有 .manifest 后缀它仍然是相同的(不是从依赖库中收集的)。稍后我将尝试使用链接器的技巧..
  • 过去总是一个可执行文件包含一整套重复的 cmets(每个目标文件中的一个)。也许他们在我不注意的时候解决了这个问题。正确的链接器脚本 fu 绝对可以解决这个问题,但我希望有一个可以重用的现有链接规则。
【解决方案3】:

您是否考虑过使用发行版的标准打包系统?在我们公司,我们有数千个包,每天都会自动部署数百个包。

我们正在使用包含所有必要信息的 debian 软件包:

  • 完整的更新日志,包括:
    • 作者;
    • 版本;
    • 更改的简短说明和时间戳。
  • 依赖信息:
    • 为使当前软件包正常工作而必须安装的所有软件包的列表。
  • 为包设置环境的安装脚本。

我认为,一旦现成的解决方案已经存在,您可能不需要以自己的方式创建清单。你可以看看debian package HowTo here

【讨论】:

  • 一个包总是可以包含多个二进制文件。远非完美。简而言之:产品版本!= 组件版本。
  • 无需为整个产品创建单个包。您可以像我们一样为系统的每个组件创建一个包。
  • 即使您的所有组件都是通过单个目录中的单个 make 构建的,您也可以使用 dh_install 将它们拆分为不同的包
  • 您确实可能选择将其拆分为不同的包,如果这是您的决定。不过,这个决定并不总是我们的。此外,我认为您正在用答案回避 OP 的问题 :) ... 一个糟糕的系统,到处都没有相同的包管理,根本没有包管理(嵌入式东西)所有这些都是直接存储上述信息的正当理由在二进制文件中,以便以后通过strings 或更复杂的工具检索。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-12-08
  • 1970-01-01
  • 2022-01-26
  • 1970-01-01
  • 1970-01-01
  • 2014-01-04
  • 2018-11-08
相关资源
最近更新 更多