diff options
Diffstat (limited to 'buildid.c')
-rw-r--r-- | buildid.c | 192 |
1 files changed, 192 insertions, 0 deletions
diff --git a/buildid.c b/buildid.c new file mode 100644 index 00000000..b27aa1fe --- /dev/null +++ b/buildid.c @@ -0,0 +1,192 @@ +/* + * Walk a list of input files, printing the name and buildid of any file + * that has one. + * + * This program is licensed under the GNU Public License version 2. + */ + +#include <err.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <getopt.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <libelf.h> +#include <gelf.h> + +static Elf_Scn *get_scn_named(Elf * elf, char *goal, GElf_Shdr * shdrp_out) +{ + int rc; + size_t shstrndx = -1; + int scn_no = 0; + Elf_Scn *scn = NULL; + GElf_Shdr shdr_data, *shdrp; + + shdrp = shdrp_out ? shdrp_out : &shdr_data; + + rc = elf_getshdrstrndx(elf, &shstrndx); + if (rc < 0) + return NULL; + + do { + GElf_Shdr *shdr; + char *name; + + scn = elf_getscn(elf, ++scn_no); + if (!scn) + break; + + shdr = gelf_getshdr(scn, shdrp); + if (!shdr) + /* + * the binary is malformed, but hey, maybe the next + * one is fine, why not... + */ + continue; + + name = elf_strptr(elf, shstrndx, shdr->sh_name); + if (name && !strcmp(name, goal)) + return scn; + } while (scn != NULL); + return NULL; +} + +static void *get_buildid(Elf * elf, size_t * sz) +{ + Elf_Scn *scn; + size_t notesz; + size_t offset = 0; + Elf_Data *data; + GElf_Shdr shdr; + + scn = get_scn_named(elf, ".note.gnu.build-id", &shdr); + if (!scn) + return NULL; + + data = elf_getdata(scn, NULL); + if (!data) + return NULL; + + do { + size_t nameoff; + size_t descoff; + GElf_Nhdr nhdr; + char *name; + + notesz = gelf_getnote(data, offset, &nhdr, &nameoff, &descoff); + if (!notesz) + break; + offset += notesz; + + if (nhdr.n_type != NT_GNU_BUILD_ID) + continue; + + name = data->d_buf + nameoff; + if (!name || strcmp(name, ELF_NOTE_GNU)) + continue; + + *sz = nhdr.n_descsz; + return data->d_buf + descoff; + } while (notesz); + return NULL; +} + +static void data2hex(uint8_t * data, size_t ds, char *str) +{ + const char hex[] = "0123456789abcdef"; + int s; + unsigned int d; + for (d = 0, s = 0; d < ds; d += 1, s += 2) { + str[s + 0] = hex[(data[d] >> 4) & 0x0f]; + str[s + 1] = hex[(data[d] >> 0) & 0x0f]; + } + str[s] = '\0'; +} + +static void handle_one(char *f) +{ + int fd; + Elf *elf; + char *b = NULL; + size_t sz; + uint8_t *data; + + if (!strcmp(f, "-")) { + fd = STDIN_FILENO; + + if ((elf = elf_begin(fd, ELF_C_READ, NULL)) == NULL) + errx(1, "Couldn't read ELF data from \"%s\"", f); + } else { + if ((fd = open(f, O_RDONLY)) < 0) + err(1, "Couldn't open \"%s\"", f); + + if ((elf = elf_begin(fd, ELF_C_READ_MMAP, NULL)) == NULL) + errx(1, "Couldn't read ELF data from \"%s\"", f); + } + + data = get_buildid(elf, &sz); + if (data) { + b = alloca(sz * 2 + 1); + data2hex(data, sz, b); + if (b) { + write(1, f, strlen(f)); + write(1, " ", 1); + write(1, b, strlen(b)); + write(1, "\n", 1); + } + } + elf_end(elf); + close(fd); +} + +static void + __attribute__ ((__noreturn__)) + usage(int status) +{ + FILE *out = status ? stderr : stdout; + + fprintf(out, "Usage: buildid [ flags | file0 [file1 [.. fileN]]]\n"); + fprintf(out, "Flags:\n"); + fprintf(out, " -h Print this help text and exit\n"); + + exit(status); +} + +int main(int argc, char **argv) +{ + int i; + struct option options[] = { + {.name = "help", + .val = '?', + }, + {.name = "usage", + .val = '?', + }, + {.name = ""} + }; + int longindex = -1; + + while ((i = getopt_long(argc, argv, "h", options, &longindex)) != -1) { + switch (i) { + case 'h': + case '?': + usage(longindex == -1 ? 1 : 0); + break; + } + } + + elf_version(EV_CURRENT); + + if (optind == argc) + usage(1); + + for (i = optind; i < argc; i++) + handle_one(argv[i]); + + return 0; +} + +// vim:fenc=utf-8:tw=75 |