程序可以读取自己的精灵部分吗?

时间:2013-07-14 08:25:46

标签: linux elf backtrace

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

3 个答案:

答案 0 :(得分:5)

  

如何从程序本身打印程序的构建版本(位于.note.gnu.build-id elf部分)?

  1. 您需要阅读ElfW(Ehdr)(在文件开头)查找二进制文件中的程序标题(.e_phoff.e_phnum将告诉您程序标题的位置,以及有多少人要阅读。

  2. 然后您阅读了程序标题,直到找到程序的PT_NOTE段。该段将告诉您偏移到二进制文件中所有注释的开头。

  3. 然后,您需要阅读ElfW(Nhdr)并跳过备注的其余部分(备注的总大小为sizeof(Nhdr) + .n_namesz + .n_descsz,正确对齐),直到您找到{{1 }}

  4. 找到.n_type == NT_GNU_BUILD_ID注释后,请跳过其NT_GNU_BUILD_ID,然后阅读.n_namesz字节以阅读实际构建ID。

  5. 您可以通过将您阅读的内容与.n_descsz的输出进行比较来验证您是否正在阅读正确的数据。

    P.S。

    如果您要解决上述解码build-id的问题,并且如果您的可执行文件未被删除,那么您可能更好地解码并打印符号而是命名(即复制readelf -n a.out所做的事情) - 它实际上比解码ELF音符更容易,因为符号表包含固定大小的条目。

答案 1 :(得分:2)

基本上,这是我根据我的问题给出的答案编写的代码。为了编译代码,我必须进行一些更改,我希望它能够适用于尽可能多的平台类型。但是,它仅在一台构建计算机上进行了测试。我使用的一个假设是程序是在运行它的机器上构建的,所以没有必要检查程序和机器之间的字节顺序兼容性。

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

答案 2 :(得分:0)

是的,程序可以读取自己的.note.gnu.build-id。重要的是dl_iterate_phdr函数。

我已经在Mesa(OpenGL / Vulkan实现)中使用了此技术,以读取其自身的build-id以便与磁盘着色器缓存一起使用。

我已将这些位提取到一个单独的项目中[1],以方便其他人使用。

[1] https://github.com/mattst88/build-id