我想使用ld的--build-id选项为我的二进制文件添加构建信息,但我不确定如何使该信息在程序内可用。假设我想编写一个程序,在每次出现异常时写入回溯,以及解析此信息的脚本。该脚本读取程序的符号表,并搜索回溯中打印的地址(因为该程序是静态链接的,backtrace_symbols无法正常工作,所以我被迫使用这样的脚本)。为了使脚本正确工作,我需要将程序的构建版本与创建回溯的程序的构建版本匹配。如何从程序本身打印程序的构建版本(位于.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.
如果您要像上面那样解码 build-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
https://dev59.com/2XTYa4cB1Zd3GeqPum-l
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
函数。#include <fcntl.h> // open
#include <unistd.h> // close
#include <stdio.h> // printf
#include <string.h> // strncmp
#include <libelf.h> // elf_*
#include <gelf.h> // gelf_*
// Compile as: gcc -o read_build_id read_build_id.c -lelf
int main(int argc, char **argv)
{
if (argc < 2)
{
printf("Usage: %s <elf-file>\n", argv[0]);
return 1;
}
// Determine the working version (the ELF version supported by both, the libelf library and this program).
if (elf_version(EV_CURRENT) == EV_NONE)
{
printf("Warning: libelf is out of date. Can't read build-id of \"%s\" (or any object file).\n", argv[1]);
return 1;
}
// Open the ELF object file.
int fd = open(argv[1], O_RDONLY);
// Open an ELF descriptor for reading.
Elf* e = elf_begin(fd, ELF_C_READ, NULL);
if (!e)
{
printf("Warning: elf_begin returned NULL for \"%s\": %s\n", argv[1], elf_errmsg(-1));
return 1;
}
if (elf_kind(e) != ELF_K_ELF)
{
printf("%s: skipping, not an ELF file.\n", argv[1]);
return 0;
}
#if 0
// Get the string table section index.
size_t shstrndx;
if (elf_getshdrstrndx(e, &shstrndx) != 0)
{
printf("elf_getshdrstrndx() failed: %s\n", elf_errmsg(-1));
return 1;
}
#endif
// Run over all sections in the ELF file.
Elf_Scn* scn = NULL;
while ((scn = elf_nextscn(e, scn)) != NULL)
{
// Get the section header.
GElf_Shdr shdr;
gelf_getshdr(scn, &shdr);
if (shdr.sh_type == SHT_NOTE)
{
#if 0
// Get the name of the section
char const* name = elf_strptr(e, shstrndx, shdr.sh_name);
printf("Section: %s\n", name);
#endif
Elf_Data* data = elf_getdata(scn, NULL);
GElf_Nhdr nhdr;
size_t name_offset, desc_offset;
size_t offset = 0;
while ((offset = gelf_getnote(data, offset, &nhdr, &name_offset, &desc_offset)) > 0)
{
if (nhdr.n_type == NT_GNU_BUILD_ID && nhdr.n_namesz == 4 && strncmp((char*)data->d_buf + name_offset, "GNU", 4) == 0)
{
printf("Found build ID: ");
unsigned char *desc = (unsigned char *)data->d_buf + desc_offset;
for (int i = 0; i < nhdr.n_descsz; ++i)
printf("%02x", desc[i]);
printf("\n");
}
}
}
}
elf_end(e);
close(fd);
}