C++二进制标识(清单)

C++ binary identification (manifest)

本文关键字:清单 标识 二进制 C++      更新时间:2023-10-16

我们有一大组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社区,有没有更好的方法来解决这个问题?

谢谢你的任何建议

在源代码(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来帮助你。但走这条路可能不值得。。。

另请查看:

  • 带有GCC的C/C++:静态地将资源文件添加到可执行文件/库和
  • 有没有相当于Windows'"资源文件">

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

如何访问符号?容易的将它们声明为外部(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: %*sn", (int)len, &_binary_hello_manifest_start);
}

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

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

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

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

asm  (".section .comment.manifestnt"
".string "hello, this is a comment"nt"
".section .text");
int main() {
....

在本例中,asm语句应位于任何函数之外。只要编译器在.text节中放入正常函数,这就应该有效。如果没有,那么你应该做出明显的替代。

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

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

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

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

  • 完整的变更日志,包括:
    • 作者
    • 版本
    • 更改的简短描述和时间戳
  • 依赖关系信息:
    • 当前程序包必须安装才能正常工作的所有程序包的列表
  • 为程序包设置环境的安装脚本

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