s390: add relocs tool
authorJosh Poimboeuf <jpoimboe@kernel.org>
Mon, 19 Feb 2024 13:27:32 +0000 (14:27 +0100)
committerHeiko Carstens <hca@linux.ibm.com>
Tue, 20 Feb 2024 13:37:33 +0000 (14:37 +0100)
This 'relocs' tool is copied from the x86 version, ported for s390, and
greatly simplified to remove unnecessary features.

It reads vmlinux and outputs assembly to create a .vmlinux.relocs_64
section which contains the offsets of all R_390_64 relocations which
apply to allocatable sections.

Acked-by: Vasily Gorbik <gor@linux.ibm.com>
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
Signed-off-by: Sumanth Korikkar <sumanthk@linux.ibm.com>
Link: https://lore.kernel.org/r/20240219132734.22881-3-sumanthk@linux.ibm.com
Signed-off-by: Heiko Carstens <hca@linux.ibm.com>
arch/s390/tools/.gitignore
arch/s390/tools/Makefile
arch/s390/tools/relocs.c [new file with mode: 0644]

index ea62f37b79ef2adfd71d77c73800e1f8498f51bb..e6af51d9d1835511e075ef5c4034314881565180 100644 (file)
@@ -1,3 +1,4 @@
 # SPDX-License-Identifier: GPL-2.0-only
 gen_facilities
 gen_opcode_table
+relocs
index f9dd47ff9ac4515de9347ce9593e4c9ed4d409c8..f2862364fb4221526708416a1ffc97cfaf810c65 100644 (file)
@@ -25,3 +25,8 @@ $(kapi)/facility-defs.h: $(obj)/gen_facilities FORCE
 
 $(kapi)/dis-defs.h: $(obj)/gen_opcode_table FORCE
        $(call filechk,dis-defs.h)
+
+hostprogs      += relocs
+PHONY          += relocs
+relocs: $(obj)/relocs
+       @:
diff --git a/arch/s390/tools/relocs.c b/arch/s390/tools/relocs.c
new file mode 100644 (file)
index 0000000..db8bcbf
--- /dev/null
@@ -0,0 +1,385 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <elf.h>
+#include <byteswap.h>
+#define USE_BSD
+#include <endian.h>
+
+#define ELF_BITS 64
+
+#define ELF_MACHINE            EM_S390
+#define ELF_MACHINE_NAME       "IBM S/390"
+#define SHT_REL_TYPE           SHT_RELA
+#define Elf_Rel                        Elf64_Rela
+
+#define ELF_CLASS              ELFCLASS64
+#define ELF_ENDIAN             ELFDATA2MSB
+#define ELF_R_SYM(val)         ELF64_R_SYM(val)
+#define ELF_R_TYPE(val)                ELF64_R_TYPE(val)
+#define ELF_ST_TYPE(o)         ELF64_ST_TYPE(o)
+#define ELF_ST_BIND(o)         ELF64_ST_BIND(o)
+#define ELF_ST_VISIBILITY(o)   ELF64_ST_VISIBILITY(o)
+
+#define ElfW(type)             _ElfW(ELF_BITS, type)
+#define _ElfW(bits, type)      __ElfW(bits, type)
+#define __ElfW(bits, type)     Elf##bits##_##type
+
+#define Elf_Addr               ElfW(Addr)
+#define Elf_Ehdr               ElfW(Ehdr)
+#define Elf_Phdr               ElfW(Phdr)
+#define Elf_Shdr               ElfW(Shdr)
+#define Elf_Sym                        ElfW(Sym)
+
+static Elf_Ehdr                ehdr;
+static unsigned long   shnum;
+static unsigned int    shstrndx;
+
+struct relocs {
+       uint32_t        *offset;
+       unsigned long   count;
+       unsigned long   size;
+};
+
+static struct relocs relocs64;
+#define FMT PRIu64
+
+struct section {
+       Elf_Shdr        shdr;
+       struct section  *link;
+       Elf_Rel         *reltab;
+};
+
+static struct section *secs;
+
+#if BYTE_ORDER == LITTLE_ENDIAN
+#define le16_to_cpu(val)       (val)
+#define le32_to_cpu(val)       (val)
+#define le64_to_cpu(val)       (val)
+#define be16_to_cpu(val)       bswap_16(val)
+#define be32_to_cpu(val)       bswap_32(val)
+#define be64_to_cpu(val)       bswap_64(val)
+#endif
+
+#if BYTE_ORDER == BIG_ENDIAN
+#define le16_to_cpu(val)       bswap_16(val)
+#define le32_to_cpu(val)       bswap_32(val)
+#define le64_to_cpu(val)       bswap_64(val)
+#define be16_to_cpu(val)       (val)
+#define be32_to_cpu(val)       (val)
+#define be64_to_cpu(val)       (val)
+#endif
+
+static uint16_t elf16_to_cpu(uint16_t val)
+{
+       if (ehdr.e_ident[EI_DATA] == ELFDATA2LSB)
+               return le16_to_cpu(val);
+       else
+               return be16_to_cpu(val);
+}
+
+static uint32_t elf32_to_cpu(uint32_t val)
+{
+       if (ehdr.e_ident[EI_DATA] == ELFDATA2LSB)
+               return le32_to_cpu(val);
+       else
+               return be32_to_cpu(val);
+}
+
+#define elf_half_to_cpu(x)     elf16_to_cpu(x)
+#define elf_word_to_cpu(x)     elf32_to_cpu(x)
+
+static uint64_t elf64_to_cpu(uint64_t val)
+{
+       return be64_to_cpu(val);
+}
+
+#define elf_addr_to_cpu(x)     elf64_to_cpu(x)
+#define elf_off_to_cpu(x)      elf64_to_cpu(x)
+#define elf_xword_to_cpu(x)    elf64_to_cpu(x)
+
+static void die(char *fmt, ...)
+{
+       va_list ap;
+
+       va_start(ap, fmt);
+       vfprintf(stderr, fmt, ap);
+       va_end(ap);
+       exit(1);
+}
+
+static void read_ehdr(FILE *fp)
+{
+       if (fread(&ehdr, sizeof(ehdr), 1, fp) != 1)
+               die("Cannot read ELF header: %s\n", strerror(errno));
+       if (memcmp(ehdr.e_ident, ELFMAG, SELFMAG) != 0)
+               die("No ELF magic\n");
+       if (ehdr.e_ident[EI_CLASS] != ELF_CLASS)
+               die("Not a %d bit executable\n", ELF_BITS);
+       if (ehdr.e_ident[EI_DATA] != ELF_ENDIAN)
+               die("ELF endian mismatch\n");
+       if (ehdr.e_ident[EI_VERSION] != EV_CURRENT)
+               die("Unknown ELF version\n");
+
+       /* Convert the fields to native endian */
+       ehdr.e_type      = elf_half_to_cpu(ehdr.e_type);
+       ehdr.e_machine   = elf_half_to_cpu(ehdr.e_machine);
+       ehdr.e_version   = elf_word_to_cpu(ehdr.e_version);
+       ehdr.e_entry     = elf_addr_to_cpu(ehdr.e_entry);
+       ehdr.e_phoff     = elf_off_to_cpu(ehdr.e_phoff);
+       ehdr.e_shoff     = elf_off_to_cpu(ehdr.e_shoff);
+       ehdr.e_flags     = elf_word_to_cpu(ehdr.e_flags);
+       ehdr.e_ehsize    = elf_half_to_cpu(ehdr.e_ehsize);
+       ehdr.e_phentsize = elf_half_to_cpu(ehdr.e_phentsize);
+       ehdr.e_phnum     = elf_half_to_cpu(ehdr.e_phnum);
+       ehdr.e_shentsize = elf_half_to_cpu(ehdr.e_shentsize);
+       ehdr.e_shnum     = elf_half_to_cpu(ehdr.e_shnum);
+       ehdr.e_shstrndx  = elf_half_to_cpu(ehdr.e_shstrndx);
+
+       shnum = ehdr.e_shnum;
+       shstrndx = ehdr.e_shstrndx;
+
+       if ((ehdr.e_type != ET_EXEC) && (ehdr.e_type != ET_DYN))
+               die("Unsupported ELF header type\n");
+       if (ehdr.e_machine != ELF_MACHINE)
+               die("Not for %s\n", ELF_MACHINE_NAME);
+       if (ehdr.e_version != EV_CURRENT)
+               die("Unknown ELF version\n");
+       if (ehdr.e_ehsize != sizeof(Elf_Ehdr))
+               die("Bad Elf header size\n");
+       if (ehdr.e_phentsize != sizeof(Elf_Phdr))
+               die("Bad program header entry\n");
+       if (ehdr.e_shentsize != sizeof(Elf_Shdr))
+               die("Bad section header entry\n");
+
+       if (shnum == SHN_UNDEF || shstrndx == SHN_XINDEX) {
+               Elf_Shdr shdr;
+
+               if (fseek(fp, ehdr.e_shoff, SEEK_SET) < 0)
+                       die("Seek to %" FMT " failed: %s\n", ehdr.e_shoff, strerror(errno));
+
+               if (fread(&shdr, sizeof(shdr), 1, fp) != 1)
+                       die("Cannot read initial ELF section header: %s\n", strerror(errno));
+
+               if (shnum == SHN_UNDEF)
+                       shnum = elf_xword_to_cpu(shdr.sh_size);
+
+               if (shstrndx == SHN_XINDEX)
+                       shstrndx = elf_word_to_cpu(shdr.sh_link);
+       }
+
+       if (shstrndx >= shnum)
+               die("String table index out of bounds\n");
+}
+
+static void read_shdrs(FILE *fp)
+{
+       Elf_Shdr shdr;
+       int i;
+
+       secs = calloc(shnum, sizeof(struct section));
+       if (!secs)
+               die("Unable to allocate %ld section headers\n", shnum);
+
+       if (fseek(fp, ehdr.e_shoff, SEEK_SET) < 0)
+               die("Seek to %" FMT " failed: %s\n", ehdr.e_shoff, strerror(errno));
+
+       for (i = 0; i < shnum; i++) {
+               struct section *sec = &secs[i];
+
+               if (fread(&shdr, sizeof(shdr), 1, fp) != 1) {
+                       die("Cannot read ELF section headers %d/%ld: %s\n",
+                           i, shnum, strerror(errno));
+               }
+
+               sec->shdr.sh_name      = elf_word_to_cpu(shdr.sh_name);
+               sec->shdr.sh_type      = elf_word_to_cpu(shdr.sh_type);
+               sec->shdr.sh_flags     = elf_xword_to_cpu(shdr.sh_flags);
+               sec->shdr.sh_addr      = elf_addr_to_cpu(shdr.sh_addr);
+               sec->shdr.sh_offset    = elf_off_to_cpu(shdr.sh_offset);
+               sec->shdr.sh_size      = elf_xword_to_cpu(shdr.sh_size);
+               sec->shdr.sh_link      = elf_word_to_cpu(shdr.sh_link);
+               sec->shdr.sh_info      = elf_word_to_cpu(shdr.sh_info);
+               sec->shdr.sh_addralign = elf_xword_to_cpu(shdr.sh_addralign);
+               sec->shdr.sh_entsize   = elf_xword_to_cpu(shdr.sh_entsize);
+
+               if (sec->shdr.sh_link < shnum)
+                       sec->link = &secs[sec->shdr.sh_link];
+       }
+
+}
+
+static void read_relocs(FILE *fp)
+{
+       int i, j;
+
+       for (i = 0; i < shnum; i++) {
+               struct section *sec = &secs[i];
+
+               if (sec->shdr.sh_type != SHT_REL_TYPE)
+                       continue;
+
+               sec->reltab = malloc(sec->shdr.sh_size);
+               if (!sec->reltab)
+                       die("malloc of %" FMT " bytes for relocs failed\n", sec->shdr.sh_size);
+
+               if (fseek(fp, sec->shdr.sh_offset, SEEK_SET) < 0)
+                       die("Seek to %" FMT " failed: %s\n", sec->shdr.sh_offset, strerror(errno));
+
+               if (fread(sec->reltab, 1, sec->shdr.sh_size, fp) != sec->shdr.sh_size)
+                       die("Cannot read symbol table: %s\n", strerror(errno));
+
+               for (j = 0; j < sec->shdr.sh_size / sizeof(Elf_Rel); j++) {
+                       Elf_Rel *rel = &sec->reltab[j];
+
+                       rel->r_offset = elf_addr_to_cpu(rel->r_offset);
+                       rel->r_info   = elf_xword_to_cpu(rel->r_info);
+#if (SHT_REL_TYPE == SHT_RELA)
+                       rel->r_addend = elf_xword_to_cpu(rel->r_addend);
+#endif
+               }
+       }
+}
+
+static void add_reloc(struct relocs *r, uint32_t offset)
+{
+       if (r->count == r->size) {
+               unsigned long newsize = r->size + 50000;
+               void *mem = realloc(r->offset, newsize * sizeof(r->offset[0]));
+
+               if (!mem)
+                       die("realloc of %ld entries for relocs failed\n", newsize);
+
+               r->offset = mem;
+               r->size = newsize;
+       }
+       r->offset[r->count++] = offset;
+}
+
+static int do_reloc(struct section *sec, Elf_Rel *rel)
+{
+       unsigned int r_type = ELF64_R_TYPE(rel->r_info);
+       ElfW(Addr) offset = rel->r_offset;
+
+       switch (r_type) {
+       case R_390_NONE:
+       case R_390_PC32:
+       case R_390_PC64:
+       case R_390_PC16DBL:
+       case R_390_PC32DBL:
+       case R_390_PLT32DBL:
+       case R_390_GOTENT:
+               break;
+       case R_390_64:
+               add_reloc(&relocs64, offset);
+               break;
+       default:
+               die("Unsupported relocation type: %d\n", r_type);
+               break;
+       }
+
+       return 0;
+}
+
+static void walk_relocs(void)
+{
+       int i;
+
+       /* Walk through the relocations */
+       for (i = 0; i < shnum; i++) {
+               struct section *sec_applies;
+               int j;
+               struct section *sec = &secs[i];
+
+               if (sec->shdr.sh_type != SHT_REL_TYPE)
+                       continue;
+
+               sec_applies = &secs[sec->shdr.sh_info];
+               if (!(sec_applies->shdr.sh_flags & SHF_ALLOC))
+                       continue;
+
+               for (j = 0; j < sec->shdr.sh_size / sizeof(Elf_Rel); j++) {
+                       Elf_Rel *rel = &sec->reltab[j];
+
+                       do_reloc(sec, rel);
+               }
+       }
+}
+
+static int cmp_relocs(const void *va, const void *vb)
+{
+       const uint32_t *a, *b;
+
+       a = va; b = vb;
+       return (*a == *b) ? 0 : (*a > *b) ? 1 : -1;
+}
+
+static void sort_relocs(struct relocs *r)
+{
+       qsort(r->offset, r->count, sizeof(r->offset[0]), cmp_relocs);
+}
+
+static int print_reloc(uint32_t v)
+{
+       return fprintf(stdout, "\t.long 0x%08"PRIx32"\n", v) > 0 ? 0 : -1;
+}
+
+static void emit_relocs(void)
+{
+       int i;
+
+       walk_relocs();
+       sort_relocs(&relocs64);
+
+       printf(".section \".vmlinux.relocs_64\",\"a\"\n");
+       for (i = 0; i < relocs64.count; i++)
+               print_reloc(relocs64.offset[i]);
+}
+
+static void process(FILE *fp)
+{
+       read_ehdr(fp);
+       read_shdrs(fp);
+       read_relocs(fp);
+       emit_relocs();
+}
+
+static void usage(void)
+{
+       die("relocs vmlinux\n");
+}
+
+int main(int argc, char **argv)
+{
+       unsigned char e_ident[EI_NIDENT];
+       const char *fname;
+       FILE *fp;
+
+       fname = NULL;
+
+       if (argc != 2)
+               usage();
+
+       fname = argv[1];
+
+       fp = fopen(fname, "r");
+       if (!fp)
+               die("Cannot open %s: %s\n", fname, strerror(errno));
+
+       if (fread(&e_ident, 1, EI_NIDENT, fp) != EI_NIDENT)
+               die("Cannot read %s: %s", fname, strerror(errno));
+
+       rewind(fp);
+
+       process(fp);
+
+       fclose(fp);
+       return 0;
+}