Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 946166af authored by Kees Cook's avatar Kees Cook Committed by H. Peter Anvin
Browse files

x86, relocs: Add 64-bit ELF support to relocs tool



This adds the ability to process relocations from the 64-bit kernel ELF,
if built with ELF_BITS=64 defined. The special case for the percpu area is
handled, along with some other symbols specific to the 64-bit kernel.

Based on work by Neill Clift and Michael Davidson.

Signed-off-by: default avatarKees Cook <keescook@chromium.org>
Link: http://lkml.kernel.org/r/1365797627-20874-4-git-send-email-keescook@chromium.org


Signed-off-by: default avatarH. Peter Anvin <hpa@linux.intel.com>
parent 5d442e63
Loading
Loading
Loading
Loading
+261 −6
Original line number Diff line number Diff line
@@ -17,19 +17,39 @@
#define _ElfW(bits, type)	__ElfW(bits, type)
#define __ElfW(bits, type)	Elf##bits##_##type

#ifndef ELF_BITS
#define ELF_BITS		32
#endif

#if (ELF_BITS == 64)
#define ELF_MACHINE             EM_X86_64
#define ELF_MACHINE_NAME        "x86_64"
#define SHT_REL_TYPE            SHT_RELA
#define Elf_Rel                 Elf64_Rela
#else
#define ELF_MACHINE		EM_386
#define ELF_MACHINE_NAME	"i386"
#define SHT_REL_TYPE		SHT_REL
#define Elf_Rel			ElfW(Rel)
#endif

#if (ELF_BITS == 64)
#define ELF_CLASS               ELFCLASS64
#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)
#else
#define ELF_CLASS		ELFCLASS32
#define ELF_R_SYM(val)		ELF32_R_SYM(val)
#define ELF_R_TYPE(val)		ELF32_R_TYPE(val)
#define ELF_ST_TYPE(o)		ELF32_ST_TYPE(o)
#define ELF_ST_BIND(o)		ELF32_ST_BIND(o)
#define ELF_ST_VISIBILITY(o)	ELF32_ST_VISIBILITY(o)
#endif

#define Elf_Rel			ElfW(Rel)
#define Elf_Addr		ElfW(Addr)
#define Elf_Ehdr		ElfW(Ehdr)
#define Elf_Phdr		ElfW(Phdr)
#define Elf_Shdr		ElfW(Shdr)
@@ -48,6 +68,7 @@ struct relocs {

static struct relocs relocs16;
static struct relocs relocs32;
static struct relocs relocs64;

struct section {
	Elf_Shdr       shdr;
@@ -77,6 +98,9 @@ static const char * const sym_regex_kernel[S_NSYMTYPES] = {
	"^(xen_irq_disable_direct_reloc$|"
	"xen_save_fl_direct_reloc$|"
	"VDSO|"
#if (ELF_BITS == 64)
	"__vvar_page|"
#endif
	"__crc_)",

/*
@@ -100,6 +124,11 @@ static const char * const sym_regex_kernel[S_NSYMTYPES] = {
	"__end_rodata|"
	"__initramfs_start|"
	"(jiffies|jiffies_64)|"
#if (ELF_BITS == 64)
	"__per_cpu_load|"
	"init_per_cpu__.*|"
	"__end_rodata_hpage_align|"
#endif
	"_end)$"
};

@@ -226,6 +255,24 @@ static const char *rel_type(unsigned type)
{
	static const char *type_name[] = {
#define REL_TYPE(X) [X] = #X
#if (ELF_BITS == 64)
		REL_TYPE(R_X86_64_NONE),
		REL_TYPE(R_X86_64_64),
		REL_TYPE(R_X86_64_PC32),
		REL_TYPE(R_X86_64_GOT32),
		REL_TYPE(R_X86_64_PLT32),
		REL_TYPE(R_X86_64_COPY),
		REL_TYPE(R_X86_64_GLOB_DAT),
		REL_TYPE(R_X86_64_JUMP_SLOT),
		REL_TYPE(R_X86_64_RELATIVE),
		REL_TYPE(R_X86_64_GOTPCREL),
		REL_TYPE(R_X86_64_32),
		REL_TYPE(R_X86_64_32S),
		REL_TYPE(R_X86_64_16),
		REL_TYPE(R_X86_64_PC16),
		REL_TYPE(R_X86_64_8),
		REL_TYPE(R_X86_64_PC8),
#else
		REL_TYPE(R_386_NONE),
		REL_TYPE(R_386_32),
		REL_TYPE(R_386_PC32),
@@ -241,6 +288,7 @@ static const char *rel_type(unsigned type)
		REL_TYPE(R_386_PC8),
		REL_TYPE(R_386_16),
		REL_TYPE(R_386_PC16),
#endif
#undef REL_TYPE
	};
	const char *name = "unknown type rel type name";
@@ -281,15 +329,42 @@ static const char *sym_name(const char *sym_strtab, Elf_Sym *sym)
	return name;
}

static Elf_Sym *sym_lookup(const char *symname)
{
	int i;
	for (i = 0; i < ehdr.e_shnum; i++) {
		struct section *sec = &secs[i];
		long nsyms;
		char *strtab;
		Elf_Sym *symtab;
		Elf_Sym *sym;

		if (sec->shdr.sh_type != SHT_SYMTAB)
			continue;

		nsyms = sec->shdr.sh_size/sizeof(Elf_Sym);
		symtab = sec->symtab;
		strtab = sec->link->strtab;

		for (sym = symtab; --nsyms >= 0; sym++) {
			if (!sym->st_name)
				continue;
			if (strcmp(symname, strtab + sym->st_name) == 0)
				return sym;
		}
	}
	return 0;
}

#if BYTE_ORDER == LITTLE_ENDIAN
#define le16_to_cpu(val) (val)
#define le32_to_cpu(val) (val)
#define le64_to_cpu(val) (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)
#endif

static uint16_t elf16_to_cpu(uint16_t val)
@@ -304,9 +379,20 @@ static uint32_t elf32_to_cpu(uint32_t val)

#define elf_half_to_cpu(x)	elf16_to_cpu(x)
#define elf_word_to_cpu(x)	elf32_to_cpu(x)

#if (ELF_BITS == 64)
static uint64_t elf64_to_cpu(uint64_t val)
{
        return le64_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)
#else
#define elf_addr_to_cpu(x)	elf32_to_cpu(x)
#define elf_off_to_cpu(x)	elf32_to_cpu(x)
#define elf_xword_to_cpu(x)	elf32_to_cpu(x)
#endif

static void read_ehdr(FILE *fp)
{
@@ -483,6 +569,9 @@ static void read_relocs(FILE *fp)
			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
		}
	}
}
@@ -491,6 +580,13 @@ static void read_relocs(FILE *fp)
static void print_absolute_symbols(void)
{
	int i;
	const char *format;

	if (ehdr.e_ident[EI_CLASS] == ELFCLASS64)
		format = "%5d %016"PRIx64" %5"PRId64" %10s %10s %12s %s\n";
	else
		format = "%5d %08"PRIx32"  %5"PRId32" %10s %10s %12s %s\n";

	printf("Absolute symbols\n");
	printf(" Num:    Value Size  Type       Bind        Visibility  Name\n");
	for (i = 0; i < ehdr.e_shnum; i++) {
@@ -510,7 +606,7 @@ static void print_absolute_symbols(void)
			if (sym->st_shndx != SHN_ABS) {
				continue;
			}
			printf("%5d %08x %5d %10s %10s %12s %s\n",
			printf(format,
				j, sym->st_value, sym->st_size,
				sym_type(ELF_ST_TYPE(sym->st_info)),
				sym_bind(ELF_ST_BIND(sym->st_info)),
@@ -524,6 +620,12 @@ static void print_absolute_symbols(void)
static void print_absolute_relocs(void)
{
	int i, printed = 0;
	const char *format;

	if (ehdr.e_ident[EI_CLASS] == ELFCLASS64)
		format = "%016"PRIx64" %016"PRIx64" %10s %016"PRIx64"  %s\n";
	else
		format = "%08"PRIx32" %08"PRIx32" %10s %08"PRIx32"  %s\n";

	for (i = 0; i < ehdr.e_shnum; i++) {
		struct section *sec = &secs[i];
@@ -576,7 +678,7 @@ static void print_absolute_relocs(void)
				printed = 1;
			}

			printf("%08x %08x %10s %08x  %s\n",
			printf(format,
				rel->r_offset,
				rel->r_info,
				rel_type(ELF_R_TYPE(rel->r_info)),
@@ -636,7 +738,139 @@ static void walk_relocs(int (*process)(struct section *sec, Elf_Rel *rel,
	}
}

static int do_reloc(struct section *sec, Elf_Rel *rel, Elf_Sym *sym,
/*
 * The .data..percpu section is a special case for x86_64 SMP kernels.
 * It is used to initialize the actual per_cpu areas and to provide
 * definitions for the per_cpu variables that correspond to their offsets
 * within the percpu area. Since the values of all of the symbols need
 * to be offsets from the start of the per_cpu area the virtual address
 * (sh_addr) of .data..percpu is 0 in SMP kernels.
 *
 * This means that:
 *
 *	Relocations that reference symbols in the per_cpu area do not
 *	need further relocation (since the value is an offset relative
 *	to the start of the per_cpu area that does not change).
 *
 *	Relocations that apply to the per_cpu area need to have their
 *	offset adjusted by by the value of __per_cpu_load to make them
 *	point to the correct place in the loaded image (because the
 *	virtual address of .data..percpu is 0).
 *
 * For non SMP kernels .data..percpu is linked as part of the normal
 * kernel data and does not require special treatment.
 *
 */
static int per_cpu_shndx	= -1;
Elf_Addr per_cpu_load_addr;

static void percpu_init(void)
{
	int i;
	for (i = 0; i < ehdr.e_shnum; i++) {
		ElfW(Sym) *sym;
		if (strcmp(sec_name(i), ".data..percpu"))
			continue;

		if (secs[i].shdr.sh_addr != 0)	/* non SMP kernel */
			return;

		sym = sym_lookup("__per_cpu_load");
		if (!sym)
			die("can't find __per_cpu_load\n");

		per_cpu_shndx = i;
		per_cpu_load_addr = sym->st_value;
		return;
	}
}

/*
 * Check to see if a symbol lies in the .data..percpu section.
 * For some as yet not understood reason the "__init_begin"
 * symbol which immediately preceeds the .data..percpu section
 * also shows up as it it were part of it so we do an explict
 * check for that symbol name and ignore it.
 */
static int is_percpu_sym(ElfW(Sym) *sym, const char *symname)
{
	return (sym->st_shndx == per_cpu_shndx) &&
		strcmp(symname, "__init_begin");
}

static int do_reloc64(struct section *sec, Elf_Rel *rel, ElfW(Sym) *sym,
		      const char *symname)
{
	unsigned r_type = ELF64_R_TYPE(rel->r_info);
	ElfW(Addr) offset = rel->r_offset;
	int shn_abs = (sym->st_shndx == SHN_ABS) && !is_reloc(S_REL, symname);

	if (sym->st_shndx == SHN_UNDEF)
		return 0;

	/*
	 * Adjust the offset if this reloc applies to the percpu section.
	 */
	if (sec->shdr.sh_info == per_cpu_shndx)
		offset += per_cpu_load_addr;

	switch (r_type) {
	case R_X86_64_NONE:
	case R_X86_64_PC32:
		/*
		 * NONE can be ignored and PC relative relocations don't
		 * need to be adjusted.
		 */
		break;

	case R_X86_64_32:
	case R_X86_64_32S:
	case R_X86_64_64:
		/*
		 * References to the percpu area don't need to be adjusted.
		 */
		if (is_percpu_sym(sym, symname))
			break;

		if (shn_abs) {
			/*
			 * Whitelisted absolute symbols do not require
			 * relocation.
			 */
			if (is_reloc(S_ABS, symname))
				break;

			die("Invalid absolute %s relocation: %s\n",
			    rel_type(r_type), symname);
			break;
		}

		/*
		 * Relocation offsets for 64 bit kernels are output
		 * as 32 bits and sign extended back to 64 bits when
		 * the relocations are processed.
		 * Make sure that the offset will fit.
		 */
		if ((int32_t)offset != (int64_t)offset)
			die("Relocation offset doesn't fit in 32 bits\n");

		if (r_type == R_X86_64_64)
			add_reloc(&relocs64, offset);
		else
			add_reloc(&relocs32, offset);
		break;

	default:
		die("Unsupported relocation type: %s (%d)\n",
		    rel_type(r_type), r_type);
		break;
	}

	return 0;
}


static int do_reloc32(struct section *sec, Elf_Rel *rel, Elf_Sym *sym,
		      const char *symname)
{
	unsigned r_type = ELF32_R_TYPE(rel->r_info);
@@ -779,9 +1013,18 @@ static void emit_relocs(int as_text, int use_real_mode)
{
	int i;
	int (*write_reloc)(uint32_t, FILE *) = write32;
	int (*do_reloc)(struct section *sec, Elf_Rel *rel, Elf_Sym *sym,
			const char *symname);

	if (ehdr.e_ident[EI_CLASS] == ELFCLASS64)
		do_reloc = do_reloc64;
	else if (!use_real_mode)
		do_reloc = do_reloc32;
	else
		do_reloc = do_reloc_real;

	/* Collect up the relocations */
	walk_relocs(use_real_mode ? do_reloc_real : do_reloc);
	walk_relocs(do_reloc);

	if (relocs16.count && !use_real_mode)
		die("Segment relocations found but --realmode not specified\n");
@@ -789,6 +1032,7 @@ static void emit_relocs(int as_text, int use_real_mode)
	/* Order the relocations for more efficient processing */
	sort_relocs(&relocs16);
	sort_relocs(&relocs32);
	sort_relocs(&relocs64);

	/* Print the relocations */
	if (as_text) {
@@ -809,6 +1053,15 @@ static void emit_relocs(int as_text, int use_real_mode)
		for (i = 0; i < relocs32.count; i++)
			write_reloc(relocs32.offset[i], stdout);
	} else {
		if (ehdr.e_ident[EI_CLASS] == ELFCLASS64) {
			/* Print a stop */
			write_reloc(0, stdout);

			/* Now print each relocation */
			for (i = 0; i < relocs64.count; i++)
				write_reloc(relocs64.offset[i], stdout);
		}

		/* Print a stop */
		write_reloc(0, stdout);

@@ -876,6 +1129,8 @@ int main(int argc, char **argv)
	read_strtabs(fp);
	read_symtabs(fp);
	read_relocs(fp);
	if (ehdr.e_ident[EI_CLASS] == ELFCLASS64)
		percpu_init();
	if (show_absolute_syms) {
		print_absolute_symbols();
		goto out;