5

I am trying to figure out how to access the build-id generated by the linker at runtime.

From this page, https://linux.die.net/man/1/ld

When I build a test program like:

% gcc test.c -o test -Wl,--build-id=sha1

I can see that the build ID is present in the binary:

% readelf -n test

Displaying notes found in: .note.gnu.build-id
  Owner                 Data size   Description
  GNU                  0x00000014   NT_GNU_BUILD_ID (unique build ID bitstring)
    Build ID: 85aa97bd52ddc4dc2a704949c2545a3a9c69c6db

I would like to print this at run-time.

EDIT: Assume you can't access the elf file from which the running process was loaded (permissions, embedded/no filesystem, etc).

EDIT: the accepted answer works, but the linker does not necessarily have to place the variable at the end of the section. If there were a way to get a pointer to the start of the section, that would be more reliable.

Todd Freed
  • 877
  • 6
  • 19

3 Answers3

5

Figured it out. Here is a working example,

#include <stdio.h>

//
// variable must have an initializer
//  https://gcc.gnu.org/onlinedocs/gcc-3.3.1/gcc/Variable-Attributes.html
//
// the linker places this immediately after the section data
// 
char build_id __attribute__((section(".note.gnu.build-id"))) = '!';

int main(int argc, char ** argv)
{
  const char * s;

  s = &build_id;

  // section data is 20 bytes in size
  s -= 20;

  // sha1 data continues for 20 bytes
  printf("  > Build ID: ");
  int x;
  for(x = 0; x < 20; x++) {
    printf("%02hhx", s[x]);
  }

  printf(" <\n");

  return 0;
}

When I run this, I get output that matches readelf,

0 % gcc -g main.c -o test -Wl,--build-id=sha1 && readelf -n test | tail -n 5 && ./test
Displaying notes found in: .note.gnu.build-id
  Owner                 Data size       Description
  GNU                  0x00000014       NT_GNU_BUILD_ID (unique build ID bitstring)
    Build ID: c5eca2cb08f4f5a31bb695955c7ebd2722ca10e9
  > Build ID: c5eca2cb08f4f5a31bb695955c7ebd2722ca10e9 <
Todd Freed
  • 877
  • 6
  • 19
  • This solution hard-codes the length of `.note.gnu.build-id`, which is less than ideal. – Employed Russian Oct 11 '21 at 17:05
  • This solution does not work on andoid. Adroid's crash dumper will stop print build id. – Smak Jan 22 '22 at 10:19
  • This seems to be working for me, but I'm getting a concerning Warning: setting incorrect section attributes for .note.gnu.build-id – mark Feb 17 '22 at 23:18
2

One possibility is to use linker scripts to get the address of the .note.gnu.build-id section:

#include <stdio.h>

// These will be set by the linker script
extern char build_id_start;
extern char build_id_end;

int main(int argc, char **argv) {
  const char *s;

  s = &build_id_start;

  // skip over header (16 bytes)
  s += 16;

  printf("  > Build ID: ");
  for (; s < &build_id_end; s++) {
    printf("%02hhx", *s);
  }

  printf(" <\n");

  return 0;
}

In the linker script, the symbols build_id_start and build_id_end are defined:

build_id_start = ADDR(.note.gnu.build-id);
build_id_end = ADDR(.note.gnu.build-id) + SIZEOF(.note.gnu.build-id);

The code then can be compiled and run:

gcc build-id.c linker-script.ld -o test && readelf -n test | grep NT_GNU_BUILD_ID -A1 && ./test
  GNU                  0x00000014   NT_GNU_BUILD_ID (unique build ID bitstring)
    Build ID: 7e87ff227443c8f2d5c8e2340638a2ec77d008a1
  > Build ID: 7e87ff227443c8f2d5c8e2340638a2ec77d008a1 <
  • "// skip over header (16 bytes)" according to what? where can I find some information about this header? I checked elf man page, can't find anything useful except Elf32_Nhdr/Elf64_Nhdr, but it doesn't match header of 16 bytes. – fujian26 Mar 22 '23 at 07:50
  • The Elf32_Nhdr/Elf64_Nhdr structure is the correct structure (with 12 bytes), but after it is the name of the note, which in this case is 4 bytes ("GNU" + null terminator). The proper way to do this would be to read the n_namesz value and to skip over 12+n_namesz bytes. – Steffen Kieß Mar 23 '23 at 20:34
1

I've found build-id library that can get BuildId in runtime.

  auto path="/path/to/executable";
  const struct build_id_note *note = build_id_find_nhdr_by_name(path);
  if (!note) {
      return std::nullopt;
  }

  ElfW(Word) len = build_id_length(note);

  const uint8_t *build_id = build_id_data(note);

  std::vector<byte> result(build_id,build_id+len);
Smak
  • 104
  • 4