一个程序能读取它自己的ELF节吗?

8
我想使用ld的--build-id选项为我的二进制文件添加构建信息,但我不确定如何使该信息在程序内可用。假设我想编写一个程序,在每次出现异常时写入回溯,以及解析此信息的脚本。该脚本读取程序的符号表,并搜索回溯中打印的地址(因为该程序是静态链接的,backtrace_symbols无法正常工作,所以我被迫使用这样的脚本)。为了使脚本正确工作,我需要将程序的构建版本与创建回溯的程序的构建版本匹配。如何从程序本身打印程序的构建版本(位于.note.gnu.build-id elf部分)?
4个回答

7
如何从程序本身打印构建版本(位于.note.gnu.build-id ELF部分中的)?
  1. 需要读取文件开头的 ElfW(Ehdr) 来查找二进制文件中的程序头(.e_phoff.e_phnum 告诉你程序头在哪里以及要读取多少个程序头)。
  2. 然后读取程序头,直到找到程序的 PT_NOTE 段。该段将告诉您二进制文件中所有注释的起始偏移量。
  3. 接下来,需要读取 ElfW(Nhdr) 并跳过其余的注释(注意:注释的总大小为 sizeof(Nhdr) + .n_namesz + .n_descsz,需要正确对齐),直到找到一个带有 .n_type == NT_GNU_BUILD_ID 的注释。
  4. 一旦找到 NT_GNU_BUILD_ID 注释,请跳过其 .n_namesz,并读取 .n_descsz 字节以读取实际的 build-id。

可以通过将读取的数据与 readelf -n a.out 命令的输出进行比较来验证是否正在读取正确的数据。

P.S.

如果您要像上面那样解码 build-id,并且 如果 您的可执行文件未被剥离,则最好只解码和打印符号名称(即复制 backtrace_symbols 的功能)--这实际上比解码 ELF 注释更容易,因为符号表包含固定大小的条目。


3

基本上,这是我根据我的问题得到的答案编写的代码。为了编译代码,我不得不进行一些更改,并希望它能在尽可能多的平台上运行。但是,它只在一个构建机器上进行了测试。我使用的假设之一是程序是在运行它的机器上构建的,因此没有必要检查程序和机器之间的字节序兼容性。

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;
}

3
是的,一个程序可以读取它自己的.note.gnu.build-id。关键是dl_iterate_phdr函数。
我已经在 Mesa(OpenGL/Vulkan实现)中使用了这种技术,以便读取其自身的build-id,用于磁盘上的着色器缓存。
我已将这些部分提取到一个单独的项目[1]中,方便他人使用。
[1] https://github.com/mattst88/build-id

0
我刚刚为这个写了自己的代码...
#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);
}

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