我想使用ld的--build-id选项将构建信息添加到我的二进制文件中。然而,我不知道如何在程序中提供这些信息。假设我想编写一个程序,在每次发生异常时编写回溯,并编写一个脚本来解析这些信息。脚本读取程序的符号表,并搜索回溯中打印的地址(我被迫使用这样的脚本,因为程序是静态链接的,并且回溯符号不起作用)。为了让脚本正确工作,我需要将程序的构建版本与创建回溯的程序的构建版相匹配。如何从程序本身打印程序的内部版本(位于.note.gnu.build-id-elf部分)?
如何从程序本身打印程序的内部版本(位于.note.gnu.build-id-elf部分)?
-
您需要读取
ElfW(Ehdr)
(在文件的开头)才能在二进制文件中找到程序头(.e_phoff
和.e_phnum
会告诉您程序头在哪里,以及要读取多少)。 -
然后读取程序头,直到找到程序的
PT_NOTE
段。这个片段会告诉你偏移到二进制文件中所有音符的开头。 -
然后,您需要阅读
ElfW(Nhdr)
并跳过注释的其余部分(注释的总大小为sizeof(Nhdr) + .n_namesz + .n_descsz
,正确对齐),直到找到带有.n_type == NT_GNU_BUILD_ID
的注释。 -
找到
NT_GNU_BUILD_ID
注释后,跳过它的.n_namesz
,读取.n_descsz
字节以读取实际的build-id。
您可以通过将读取的数据与readelf -n a.out
的输出进行比较来验证您是否读取了正确的数据。
p.S.
如果你要像上面那样解码构建id,并且如果你的可执行文件没有被剥离,那么你最好只解码并打印符号名称(即复制backtrace_symbols
的功能)——这实际上比解码ELF注释更容易,因为符号表包含固定大小的条目。
基本上,这是我根据问题的答案编写的代码。为了编译代码,我不得不做一些更改,我希望它能适用于尽可能多的平台类型。然而,它只在一台构建机器上进行了测试。我使用的一个假设是,程序是在运行它的机器上构建的,因此检查程序和机器之间的字节序兼容性毫无意义。
user@:~/$ uname -s -r -m -o
Linux 3.2.0-45-generic x86_64 GNU/Linux
user@:~/$ g++ test.cpp -o test
user@:~/$ readelf -n test | grep Build
Build ID: dc5c4682e0282e2bd8bc2d3b61cfe35826aa34fc
user@:~/$ ./test
Build ID: dc5c4682e0282e2bd8bc2d3b61cfe35826aa34fc
#include <elf.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#if __x86_64__
# define ElfW(type) Elf64_##type
#else
# define ElfW(type) Elf32_##type
#endif
/*
detecting build id of a program from its note section
http://stackoverflow.com/questions/17637745/can-a-program-read-its-own-elf-section
http://www.scs.stanford.edu/histar/src/pkg/uclibc/utils/readelf.c
http://www.sco.com/developers/gabi/2000-07-17/ch5.pheader.html#note_section
*/
int main (int argc, char* argv[])
{
char *thefilename = argv[0];
FILE *thefile;
struct stat statbuf;
ElfW(Ehdr) *ehdr = 0;
ElfW(Phdr) *phdr = 0;
ElfW(Nhdr) *nhdr = 0;
if (!(thefile = fopen(thefilename, "r"))) {
perror(thefilename);
exit(EXIT_FAILURE);
}
if (fstat(fileno(thefile), &statbuf) < 0) {
perror(thefilename);
exit(EXIT_FAILURE);
}
ehdr = (ElfW(Ehdr) *)mmap(0, statbuf.st_size,
PROT_READ|PROT_WRITE, MAP_PRIVATE, fileno(thefile), 0);
phdr = (ElfW(Phdr) *)(ehdr->e_phoff + (size_t)ehdr);
while (phdr->p_type != PT_NOTE)
{
++phdr;
}
nhdr = (ElfW(Nhdr) *)(phdr->p_offset + (size_t)ehdr);
while (nhdr->n_type != NT_GNU_BUILD_ID)
{
nhdr = (ElfW(Nhdr) *)((size_t)nhdr + sizeof(ElfW(Nhdr)) + nhdr->n_namesz + nhdr->n_descsz);
}
unsigned char * build_id = (unsigned char *)malloc(nhdr->n_descsz);
memcpy(build_id, (void *)((size_t)nhdr + sizeof(ElfW(Nhdr)) + nhdr->n_namesz), nhdr->n_descsz);
printf(" Build ID: ");
for (int i = 0 ; i < nhdr->n_descsz ; ++i)
{
printf("%02x",build_id[i]);
}
free(build_id);
printf("n");
return 0;
}
.note.gnu.build-id
。其中最重要的部分是dl_iterate_phdr
函数。
我在Mesa(OpenGL/Vulkan实现)中使用了这种技术来读取它自己的构建id,以便与磁盘着色器缓存一起使用。
我已经将这些部分提取到一个单独的项目[1]中,以便其他人使用。
[1]https://github.com/mattst88/build-id