Access build-id at runtime
Asked Answered
A

3

7

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.

Alcoholize answered 11/4, 2019 at 22:12 Comment(8)
Have you tried inline assembly?Admiration
No, but now I'm intriguedAlcoholize
Your answer is not at all guaranteed to work. "the linker places this immediately after ..." or it can place it before. Also, --build-id can be SHA1, or MD5, or an arbitrary hex value.Rudie
I've used -Wl,--build-id=sha1 in all of the examplesAlcoholize
Please answer your own question and accept it instead of answering the question inside the question.Admiration
moved the solution into an answer.Alcoholize
Possible duplicate of can a program read its own elf section?Veinstone
I don't think it's a duplicate, because I want a solution that works when you don't have access to the un-loaded elf file.Alcoholize
A
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 <
Alcoholize answered 13/4, 2019 at 17:46 Comment(4)
This solution hard-codes the length of .note.gnu.build-id, which is less than ideal.Rudie
This solution does not work on andoid. Adroid's crash dumper will stop print build id.Brindisi
This seems to be working for me, but I'm getting a concerning Warning: setting incorrect section attributes for .note.gnu.build-idTwittery
This isn't the answer I was looking for: I need to be able to read the build-id of an arbitrary library (that has set it). In that case I have of course access to that ELF file and will need a way to read the .note.gnu.build-id ELF note section.Kinzer
P
3

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 <
Pops answered 28/12, 2019 at 16:43 Comment(3)
"// 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.Known
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.Distant
For reference since the start of the section that you define here is the header, this can be done by getting the header Elf64_Nhdr *hdr = (Elf64_Nhdr *)&__build_id_start; and then calculating the size const char *s = s = &__build_id_start + sizeof(Elf64_Nhdr) + hdr->n_namesz;Encephalography
B
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);
Brindisi answered 18/1, 2022 at 21:13 Comment(1)
Glad it works for you :)Hokusai

© 2022 - 2024 — McMap. All rights reserved.