Commit 5d120458 authored by Linus Torvalds's avatar Linus Torvalds

Merge branch 'x86/ld-fix' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip

Pull x86 linker bug workarounds from Peter Anvin.

GNU ld-2.22.52.0.[12] (*) has an unfortunate bug where it incorrectly
turns certain relocation entries absolute.  Section-relative symbols
that are part of otherwise empty sections are silently changed them to
absolute.  We rely on section-relative symbols staying section-relative,
and actually have several sections in the linker script solely for this
purpose.

See for example

   http://sourceware.org/bugzilla/show_bug.cgi?id=14052

We could just black-list the buggy linker, but it appears that it got
shipped in at least F17, and possibly other distros too, so it's sadly
not some rare unusual case.

This backports the workaround from the x86/trampoline branch, and as
Peter says: "This is not a minimal fix, not at all, but it is a tested
code base."

* 'x86/ld-fix' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip:
  x86, relocs: When printing an error, say relative or absolute
  x86, relocs: Workaround for binutils 2.22.52.0.1 section bug
  x86, realmode: 16-bit real-mode code support for relocs tool

(*) That's a manly release numbering system. Stupid, sure. But manly.
parents 14e931a2 24ab82bd
...@@ -442,7 +442,7 @@ asm-generic: ...@@ -442,7 +442,7 @@ asm-generic:
no-dot-config-targets := clean mrproper distclean \ no-dot-config-targets := clean mrproper distclean \
cscope gtags TAGS tags help %docs check% coccicheck \ cscope gtags TAGS tags help %docs check% coccicheck \
include/linux/version.h headers_% archheaders \ include/linux/version.h headers_% archheaders archscripts \
kernelversion %src-pkg kernelversion %src-pkg
config-targets := 0 config-targets := 0
...@@ -979,7 +979,7 @@ prepare1: prepare2 include/linux/version.h include/generated/utsrelease.h \ ...@@ -979,7 +979,7 @@ prepare1: prepare2 include/linux/version.h include/generated/utsrelease.h \
include/config/auto.conf include/config/auto.conf
$(cmd_crmodverdir) $(cmd_crmodverdir)
archprepare: archheaders prepare1 scripts_basic archprepare: archheaders archscripts prepare1 scripts_basic
prepare0: archprepare FORCE prepare0: archprepare FORCE
$(Q)$(MAKE) $(build)=. $(Q)$(MAKE) $(build)=.
...@@ -1049,8 +1049,11 @@ hdr-dst = $(if $(KBUILD_HEADERS), dst=include/asm-$(hdr-arch), dst=include/asm) ...@@ -1049,8 +1049,11 @@ hdr-dst = $(if $(KBUILD_HEADERS), dst=include/asm-$(hdr-arch), dst=include/asm)
PHONY += archheaders PHONY += archheaders
archheaders: archheaders:
PHONY += archscripts
archscripts:
PHONY += __headers PHONY += __headers
__headers: include/linux/version.h scripts_basic asm-generic archheaders FORCE __headers: include/linux/version.h scripts_basic asm-generic archheaders archscripts FORCE
$(Q)$(MAKE) $(build)=scripts build_unifdef $(Q)$(MAKE) $(build)=scripts build_unifdef
PHONY += headers_install_all PHONY += headers_install_all
......
...@@ -134,6 +134,9 @@ KBUILD_CFLAGS += $(call cc-option,-mno-avx,) ...@@ -134,6 +134,9 @@ KBUILD_CFLAGS += $(call cc-option,-mno-avx,)
KBUILD_CFLAGS += $(mflags-y) KBUILD_CFLAGS += $(mflags-y)
KBUILD_AFLAGS += $(mflags-y) KBUILD_AFLAGS += $(mflags-y)
archscripts:
$(Q)$(MAKE) $(build)=arch/x86/tools relocs
### ###
# Syscall table generation # Syscall table generation
......
...@@ -40,13 +40,12 @@ OBJCOPYFLAGS_vmlinux.bin := -R .comment -S ...@@ -40,13 +40,12 @@ OBJCOPYFLAGS_vmlinux.bin := -R .comment -S
$(obj)/vmlinux.bin: vmlinux FORCE $(obj)/vmlinux.bin: vmlinux FORCE
$(call if_changed,objcopy) $(call if_changed,objcopy)
targets += vmlinux.bin.all vmlinux.relocs
targets += vmlinux.bin.all vmlinux.relocs relocs CMD_RELOCS = arch/x86/tools/relocs
hostprogs-$(CONFIG_X86_NEED_RELOCS) += relocs
quiet_cmd_relocs = RELOCS $@ quiet_cmd_relocs = RELOCS $@
cmd_relocs = $(obj)/relocs $< > $@;$(obj)/relocs --abs-relocs $< cmd_relocs = $(CMD_RELOCS) $< > $@;$(CMD_RELOCS) --abs-relocs $<
$(obj)/vmlinux.relocs: vmlinux $(obj)/relocs FORCE $(obj)/vmlinux.relocs: vmlinux FORCE
$(call if_changed,relocs) $(call if_changed,relocs)
vmlinux.bin.all-y := $(obj)/vmlinux.bin vmlinux.bin.all-y := $(obj)/vmlinux.bin
......
...@@ -36,3 +36,7 @@ HOSTCFLAGS_insn_sanity.o := -Wall -I$(objtree)/arch/x86/lib/ -I$(srctree)/arch/x ...@@ -36,3 +36,7 @@ HOSTCFLAGS_insn_sanity.o := -Wall -I$(objtree)/arch/x86/lib/ -I$(srctree)/arch/x
$(obj)/test_get_len.o: $(srctree)/arch/x86/lib/insn.c $(srctree)/arch/x86/lib/inat.c $(srctree)/arch/x86/include/asm/inat_types.h $(srctree)/arch/x86/include/asm/inat.h $(srctree)/arch/x86/include/asm/insn.h $(objtree)/arch/x86/lib/inat-tables.c $(obj)/test_get_len.o: $(srctree)/arch/x86/lib/insn.c $(srctree)/arch/x86/lib/inat.c $(srctree)/arch/x86/include/asm/inat_types.h $(srctree)/arch/x86/include/asm/inat.h $(srctree)/arch/x86/include/asm/insn.h $(objtree)/arch/x86/lib/inat-tables.c
$(obj)/insn_sanity.o: $(srctree)/arch/x86/lib/insn.c $(srctree)/arch/x86/lib/inat.c $(srctree)/arch/x86/include/asm/inat_types.h $(srctree)/arch/x86/include/asm/inat.h $(srctree)/arch/x86/include/asm/insn.h $(objtree)/arch/x86/lib/inat-tables.c $(obj)/insn_sanity.o: $(srctree)/arch/x86/lib/insn.c $(srctree)/arch/x86/lib/inat.c $(srctree)/arch/x86/include/asm/inat_types.h $(srctree)/arch/x86/include/asm/inat.h $(srctree)/arch/x86/include/asm/insn.h $(objtree)/arch/x86/lib/inat-tables.c
HOST_EXTRACFLAGS += -I$(srctree)/tools/include
hostprogs-y += relocs
relocs: $(obj)/relocs
...@@ -18,6 +18,8 @@ static void die(char *fmt, ...); ...@@ -18,6 +18,8 @@ static void die(char *fmt, ...);
static Elf32_Ehdr ehdr; static Elf32_Ehdr ehdr;
static unsigned long reloc_count, reloc_idx; static unsigned long reloc_count, reloc_idx;
static unsigned long *relocs; static unsigned long *relocs;
static unsigned long reloc16_count, reloc16_idx;
static unsigned long *relocs16;
struct section { struct section {
Elf32_Shdr shdr; Elf32_Shdr shdr;
...@@ -28,52 +30,86 @@ struct section { ...@@ -28,52 +30,86 @@ struct section {
}; };
static struct section *secs; static struct section *secs;
enum symtype {
S_ABS,
S_REL,
S_SEG,
S_LIN,
S_NSYMTYPES
};
static const char * const sym_regex_kernel[S_NSYMTYPES] = {
/* /*
* Following symbols have been audited. There values are constant and do * Following symbols have been audited. There values are constant and do
* not change if bzImage is loaded at a different physical address than * not change if bzImage is loaded at a different physical address than
* the address for which it has been compiled. Don't warn user about * the address for which it has been compiled. Don't warn user about
* absolute relocations present w.r.t these symbols. * absolute relocations present w.r.t these symbols.
*/ */
static const char abs_sym_regex[] = [S_ABS] =
"^(xen_irq_disable_direct_reloc$|" "^(xen_irq_disable_direct_reloc$|"
"xen_save_fl_direct_reloc$|" "xen_save_fl_direct_reloc$|"
"VDSO|" "VDSO|"
"__crc_)"; "__crc_)",
static regex_t abs_sym_regex_c;
static int is_abs_reloc(const char *sym_name)
{
return !regexec(&abs_sym_regex_c, sym_name, 0, NULL, 0);
}
/* /*
* These symbols are known to be relative, even if the linker marks them * These symbols are known to be relative, even if the linker marks them
* as absolute (typically defined outside any section in the linker script.) * as absolute (typically defined outside any section in the linker script.)
*/ */
static const char rel_sym_regex[] = [S_REL] =
"^_end$"; "^(__init_(begin|end)|"
static regex_t rel_sym_regex_c; "__x86_cpu_dev_(start|end)|"
static int is_rel_reloc(const char *sym_name) "(__parainstructions|__alt_instructions)(|_end)|"
"(__iommu_table|__apicdrivers|__smp_locks)(|_end)|"
"_end)$"
};
static const char * const sym_regex_realmode[S_NSYMTYPES] = {
/*
* These are 16-bit segment symbols when compiling 16-bit code.
*/
[S_SEG] =
"^real_mode_seg$",
/*
* These are offsets belonging to segments, as opposed to linear addresses,
* when compiling 16-bit code.
*/
[S_LIN] =
"^pa_",
};
static const char * const *sym_regex;
static regex_t sym_regex_c[S_NSYMTYPES];
static int is_reloc(enum symtype type, const char *sym_name)
{ {
return !regexec(&rel_sym_regex_c, sym_name, 0, NULL, 0); return sym_regex[type] &&
!regexec(&sym_regex_c[type], sym_name, 0, NULL, 0);
} }
static void regex_init(void) static void regex_init(int use_real_mode)
{ {
char errbuf[128]; char errbuf[128];
int err; int err;
int i;
err = regcomp(&abs_sym_regex_c, abs_sym_regex,
REG_EXTENDED|REG_NOSUB); if (use_real_mode)
if (err) { sym_regex = sym_regex_realmode;
regerror(err, &abs_sym_regex_c, errbuf, sizeof errbuf); else
die("%s", errbuf); sym_regex = sym_regex_kernel;
}
err = regcomp(&rel_sym_regex_c, rel_sym_regex, for (i = 0; i < S_NSYMTYPES; i++) {
REG_EXTENDED|REG_NOSUB); if (!sym_regex[i])
if (err) { continue;
regerror(err, &rel_sym_regex_c, errbuf, sizeof errbuf);
die("%s", errbuf); err = regcomp(&sym_regex_c[i], sym_regex[i],
REG_EXTENDED|REG_NOSUB);
if (err) {
regerror(err, &sym_regex_c[i], errbuf, sizeof errbuf);
die("%s", errbuf);
}
} }
} }
...@@ -154,6 +190,10 @@ static const char *rel_type(unsigned type) ...@@ -154,6 +190,10 @@ static const char *rel_type(unsigned type)
REL_TYPE(R_386_RELATIVE), REL_TYPE(R_386_RELATIVE),
REL_TYPE(R_386_GOTOFF), REL_TYPE(R_386_GOTOFF),
REL_TYPE(R_386_GOTPC), REL_TYPE(R_386_GOTPC),
REL_TYPE(R_386_8),
REL_TYPE(R_386_PC8),
REL_TYPE(R_386_16),
REL_TYPE(R_386_PC16),
#undef REL_TYPE #undef REL_TYPE
}; };
const char *name = "unknown type rel type name"; const char *name = "unknown type rel type name";
...@@ -189,7 +229,7 @@ static const char *sym_name(const char *sym_strtab, Elf32_Sym *sym) ...@@ -189,7 +229,7 @@ static const char *sym_name(const char *sym_strtab, Elf32_Sym *sym)
name = sym_strtab + sym->st_name; name = sym_strtab + sym->st_name;
} }
else { else {
name = sec_name(secs[sym->st_shndx].shdr.sh_name); name = sec_name(sym->st_shndx);
} }
return name; return name;
} }
...@@ -472,7 +512,7 @@ static void print_absolute_relocs(void) ...@@ -472,7 +512,7 @@ static void print_absolute_relocs(void)
* Before warning check if this absolute symbol * Before warning check if this absolute symbol
* relocation is harmless. * relocation is harmless.
*/ */
if (is_abs_reloc(name) || is_rel_reloc(name)) if (is_reloc(S_ABS, name) || is_reloc(S_REL, name))
continue; continue;
if (!printed) { if (!printed) {
...@@ -496,7 +536,8 @@ static void print_absolute_relocs(void) ...@@ -496,7 +536,8 @@ static void print_absolute_relocs(void)
printf("\n"); printf("\n");
} }
static void walk_relocs(void (*visit)(Elf32_Rel *rel, Elf32_Sym *sym)) static void walk_relocs(void (*visit)(Elf32_Rel *rel, Elf32_Sym *sym),
int use_real_mode)
{ {
int i; int i;
/* Walk through the relocations */ /* Walk through the relocations */
...@@ -521,30 +562,67 @@ static void walk_relocs(void (*visit)(Elf32_Rel *rel, Elf32_Sym *sym)) ...@@ -521,30 +562,67 @@ static void walk_relocs(void (*visit)(Elf32_Rel *rel, Elf32_Sym *sym))
Elf32_Rel *rel; Elf32_Rel *rel;
Elf32_Sym *sym; Elf32_Sym *sym;
unsigned r_type; unsigned r_type;
const char *symname;
int shn_abs;
rel = &sec->reltab[j]; rel = &sec->reltab[j];
sym = &sh_symtab[ELF32_R_SYM(rel->r_info)]; sym = &sh_symtab[ELF32_R_SYM(rel->r_info)];
r_type = ELF32_R_TYPE(rel->r_info); r_type = ELF32_R_TYPE(rel->r_info);
/* Don't visit relocations to absolute symbols */
if (sym->st_shndx == SHN_ABS && shn_abs = sym->st_shndx == SHN_ABS;
!is_rel_reloc(sym_name(sym_strtab, sym))) {
continue;
}
switch (r_type) { switch (r_type) {
case R_386_NONE: case R_386_NONE:
case R_386_PC32: case R_386_PC32:
case R_386_PC16:
case R_386_PC8:
/* /*
* NONE can be ignored and and PC relative * NONE can be ignored and and PC relative
* relocations don't need to be adjusted. * relocations don't need to be adjusted.
*/ */
break; break;
case R_386_16:
symname = sym_name(sym_strtab, sym);
if (!use_real_mode)
goto bad;
if (shn_abs) {
if (is_reloc(S_ABS, symname))
break;
else if (!is_reloc(S_SEG, symname))
goto bad;
} else {
if (is_reloc(S_LIN, symname))
goto bad;
else
break;
}
visit(rel, sym);
break;
case R_386_32: case R_386_32:
/* Visit relocations that need to be adjusted */ symname = sym_name(sym_strtab, sym);
if (shn_abs) {
if (is_reloc(S_ABS, symname))
break;
else if (!is_reloc(S_REL, symname))
goto bad;
} else {
if (use_real_mode &&
!is_reloc(S_LIN, symname))
break;
}
visit(rel, sym); visit(rel, sym);
break; break;
default: default:
die("Unsupported relocation type: %s (%d)\n", die("Unsupported relocation type: %s (%d)\n",
rel_type(r_type), r_type); rel_type(r_type), r_type);
break; break;
bad:
symname = sym_name(sym_strtab, sym);
die("Invalid %s %s relocation: %s\n",
shn_abs ? "absolute" : "relative",
rel_type(r_type), symname);
} }
} }
} }
...@@ -552,13 +630,19 @@ static void walk_relocs(void (*visit)(Elf32_Rel *rel, Elf32_Sym *sym)) ...@@ -552,13 +630,19 @@ static void walk_relocs(void (*visit)(Elf32_Rel *rel, Elf32_Sym *sym))
static void count_reloc(Elf32_Rel *rel, Elf32_Sym *sym) static void count_reloc(Elf32_Rel *rel, Elf32_Sym *sym)
{ {
reloc_count += 1; if (ELF32_R_TYPE(rel->r_info) == R_386_16)
reloc16_count++;
else
reloc_count++;
} }
static void collect_reloc(Elf32_Rel *rel, Elf32_Sym *sym) static void collect_reloc(Elf32_Rel *rel, Elf32_Sym *sym)
{ {
/* Remember the address that needs to be adjusted. */ /* Remember the address that needs to be adjusted. */
relocs[reloc_idx++] = rel->r_offset; if (ELF32_R_TYPE(rel->r_info) == R_386_16)
relocs16[reloc16_idx++] = rel->r_offset;
else
relocs[reloc_idx++] = rel->r_offset;
} }
static int cmp_relocs(const void *va, const void *vb) static int cmp_relocs(const void *va, const void *vb)
...@@ -568,23 +652,41 @@ static int cmp_relocs(const void *va, const void *vb) ...@@ -568,23 +652,41 @@ static int cmp_relocs(const void *va, const void *vb)
return (*a == *b)? 0 : (*a > *b)? 1 : -1; return (*a == *b)? 0 : (*a > *b)? 1 : -1;
} }
static void emit_relocs(int as_text) static int write32(unsigned int v, FILE *f)
{
unsigned char buf[4];
put_unaligned_le32(v, buf);
return fwrite(buf, 1, 4, f) == 4 ? 0 : -1;
}
static void emit_relocs(int as_text, int use_real_mode)
{ {
int i; int i;
/* Count how many relocations I have and allocate space for them. */ /* Count how many relocations I have and allocate space for them. */
reloc_count = 0; reloc_count = 0;
walk_relocs(count_reloc); walk_relocs(count_reloc, use_real_mode);
relocs = malloc(reloc_count * sizeof(relocs[0])); relocs = malloc(reloc_count * sizeof(relocs[0]));
if (!relocs) { if (!relocs) {
die("malloc of %d entries for relocs failed\n", die("malloc of %d entries for relocs failed\n",
reloc_count); reloc_count);
} }
relocs16 = malloc(reloc16_count * sizeof(relocs[0]));
if (!relocs16) {
die("malloc of %d entries for relocs16 failed\n",
reloc16_count);
}
/* Collect up the relocations */ /* Collect up the relocations */
reloc_idx = 0; reloc_idx = 0;
walk_relocs(collect_reloc); walk_relocs(collect_reloc, use_real_mode);
if (reloc16_count && !use_real_mode)
die("Segment relocations found but --realmode not specified\n");
/* Order the relocations for more efficient processing */ /* Order the relocations for more efficient processing */
qsort(relocs, reloc_count, sizeof(relocs[0]), cmp_relocs); qsort(relocs, reloc_count, sizeof(relocs[0]), cmp_relocs);
qsort(relocs16, reloc16_count, sizeof(relocs16[0]), cmp_relocs);
/* Print the relocations */ /* Print the relocations */
if (as_text) { if (as_text) {
...@@ -593,58 +695,83 @@ static void emit_relocs(int as_text) ...@@ -593,58 +695,83 @@ static void emit_relocs(int as_text)
*/ */
printf(".section \".data.reloc\",\"a\"\n"); printf(".section \".data.reloc\",\"a\"\n");
printf(".balign 4\n"); printf(".balign 4\n");
for (i = 0; i < reloc_count; i++) { if (use_real_mode) {
printf("\t .long 0x%08lx\n", relocs[i]); printf("\t.long %lu\n", reloc16_count);
for (i = 0; i < reloc16_count; i++)
printf("\t.long 0x%08lx\n", relocs16[i]);
printf("\t.long %lu\n", reloc_count);
for (i = 0; i < reloc_count; i++) {
printf("\t.long 0x%08lx\n", relocs[i]);
}
} else {
/* Print a stop */
printf("\t.long 0x%08lx\n", (unsigned long)0);
for (i = 0; i < reloc_count; i++) {
printf("\t.long 0x%08lx\n", relocs[i]);
}
} }
printf("\n"); printf("\n");
} }
else { else {
unsigned char buf[4]; if (use_real_mode) {
/* Print a stop */ write32(reloc16_count, stdout);
fwrite("\0\0\0\0", 4, 1, stdout); for (i = 0; i < reloc16_count; i++)
/* Now print each relocation */ write32(relocs16[i], stdout);
for (i = 0; i < reloc_count; i++) { write32(reloc_count, stdout);
put_unaligned_le32(relocs[i], buf);
fwrite(buf, 4, 1, stdout); /* Now print each relocation */
for (i = 0; i < reloc_count; i++)
write32(relocs[i], stdout);
} else {
/* Print a stop */
write32(0, stdout);
/* Now print each relocation */
for (i = 0; i < reloc_count; i++) {
write32(relocs[i], stdout);
}
} }
} }
} }
static void usage(void) static void usage(void)
{ {
die("relocs [--abs-syms |--abs-relocs | --text] vmlinux\n"); die("relocs [--abs-syms|--abs-relocs|--text|--realmode] vmlinux\n");
} }
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
int show_absolute_syms, show_absolute_relocs; int show_absolute_syms, show_absolute_relocs;
int as_text; int as_text, use_real_mode;
const char *fname; const char *fname;
FILE *fp; FILE *fp;
int i; int i;
regex_init();
show_absolute_syms = 0; show_absolute_syms = 0;
show_absolute_relocs = 0; show_absolute_relocs = 0;
as_text = 0; as_text = 0;
use_real_mode = 0;
fname = NULL; fname = NULL;
for (i = 1; i < argc; i++) { for (i = 1; i < argc; i++) {
char *arg = argv[i]; char *arg = argv[i];
if (*arg == '-') { if (*arg == '-') {
if (strcmp(argv[1], "--abs-syms") == 0) { if (strcmp(arg, "--abs-syms") == 0) {
show_absolute_syms = 1; show_absolute_syms = 1;
continue; continue;
} }
if (strcmp(arg, "--abs-relocs") == 0) {
if (strcmp(argv[1], "--abs-relocs") == 0) {
show_absolute_relocs = 1; show_absolute_relocs = 1;
continue; continue;
} }
else if (strcmp(argv[1], "--text") == 0) { if (strcmp(arg, "--text") == 0) {
as_text = 1; as_text = 1;
continue; continue;
} }
if (strcmp(arg, "--realmode") == 0) {
use_real_mode = 1;
continue;
}
} }
else if (!fname) { else if (!fname) {
fname = arg; fname = arg;
...@@ -655,6 +782,7 @@ int main(int argc, char **argv) ...@@ -655,6 +782,7 @@ int main(int argc, char **argv)
if (!fname) { if (!fname) {
usage(); usage();
} }
regex_init(use_real_mode);
fp = fopen(fname, "r"); fp = fopen(fname, "r");
if (!fp) { if (!fp) {
die("Cannot open %s: %s\n", die("Cannot open %s: %s\n",
...@@ -673,6 +801,6 @@ int main(int argc, char **argv) ...@@ -673,6 +801,6 @@ int main(int argc, char **argv)
print_absolute_relocs(); print_absolute_relocs();
return 0; return 0;
} }
emit_relocs(as_text); emit_relocs(as_text, use_real_mode);
return 0; return 0;
} }
...@@ -8,6 +8,8 @@ ...@@ -8,6 +8,8 @@
# conmakehash: Create arrays for initializing the kernel console tables # conmakehash: Create arrays for initializing the kernel console tables
# docproc: Used in Documentation/DocBook # docproc: Used in Documentation/DocBook
HOST_EXTRACFLAGS += -I$(srctree)/tools/include
hostprogs-$(CONFIG_KALLSYMS) += kallsyms hostprogs-$(CONFIG_KALLSYMS) += kallsyms
hostprogs-$(CONFIG_LOGO) += pnmtologo hostprogs-$(CONFIG_LOGO) += pnmtologo
hostprogs-$(CONFIG_VT) += conmakehash hostprogs-$(CONFIG_VT) += conmakehash
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment