aboutsummaryrefslogtreecommitdiff
path: root/sys
diff options
context:
space:
mode:
authorAdrian Chadd <adrian@FreeBSD.org>2016-08-21 00:48:41 +0000
committerAdrian Chadd <adrian@FreeBSD.org>2016-08-21 00:48:41 +0000
commit1a71a7f2a4a4cef9283150b203df0aa64d0d1ac9 (patch)
treed17da29ba7a88e74c84e4969b746bfcf74c0627f /sys
parent9da85a912dfc0d00fc73b1f94cf3c97d576f85a2 (diff)
downloadsrc-1a71a7f2a4a4cef9283150b203df0aa64d0d1ac9.tar.gz
src-1a71a7f2a4a4cef9283150b203df0aa64d0d1ac9.zip
[mips] add support for the "creative" GNU extensions and IRIX hilarity around MIPS LO16/HI16 relocations.
This was .. an interesting headache. There are two halves: * The earlier IRIX stuff (yes, early) occasionally would do dead code removal and generate multiple consecutive LO16 entries. If this is done for REL entries then it's fine - there's no state kept between them. But gcc 5.x seems to do this for RELA entries. eg: HI1 LO1 HI2 LO2 LO3 HI4 LO4 .. in this instance, LO2 should affect HI2, but LO3 doesn't at all affect anything. The matching HI3 was in code that was deleted as "dead code". Then, the next one: * A "GCC extension" allows for multiple HI entries before a LO entry; and all of those HI entries use the first LO entry as their basis for RELA offset calculations. It does this so GCC can also do dead code deletion without necessarily having to geneate fake relocation entries for balanced HI/LO RELA entries. eg: HI1 LO1 HI2 HI3 HI4 LO4 LO5 HI6 LO6 LO7 in this instance, HI{2,3,4} are the same relocation as LO4 (eg .bss) and need to be buffered until LO4 - then the RELA offset is applied from LO4 to HI{2,3,4} calculations. /And/, the AHL from HI4 is used during the LO4 relocation calculation, just like in the normal (ie, before this commit) implementation. Then, LO5 doesn't trigger anything - the HI "buffer" is empty, so there are no HI relocations to flush out. HI6/LO6 are normal, and LO7 doesn't trigger any HI updates. Tested: * AR9344 SoC, kernel modules, using gcc-5.3 (mips-gcc-5.3.0 package) Notes: * Yes, I do feel dirty having written this code. Reviewed by: imp (after a handful of "this should be on fire" moments wrt gcc and this code)
Notes
Notes: svn path=/head/; revision=304552
Diffstat (limited to 'sys')
-rw-r--r--sys/mips/mips/elf_machdep.c230
1 files changed, 210 insertions, 20 deletions
diff --git a/sys/mips/mips/elf_machdep.c b/sys/mips/mips/elf_machdep.c
index b332bf37d5d7..5f4161b259c1 100644
--- a/sys/mips/mips/elf_machdep.c
+++ b/sys/mips/mips/elf_machdep.c
@@ -161,6 +161,136 @@ elf32_dump_thread(struct thread *td __unused, void *dst __unused,
}
#endif
+/*
+ * The following MIPS relocation code for tracking multiple
+ * consecutive HI32/LO32 entries is because of the following:
+ *
+ * https://dmz-portal.mips.com/wiki/MIPS_relocation_types
+ *
+ * ===
+ *
+ * + R_MIPS_HI16
+ *
+ * An R_MIPS_HI16 must be followed eventually by an associated R_MIPS_LO16
+ * relocation record in the same SHT_REL section. The contents of the two
+ * fields to be relocated are combined to form a full 32-bit addend AHL.
+ * An R_MIPS_LO16 entry which does not immediately follow a R_MIPS_HI16 is
+ * combined with the most recent one encountered, i.e. multiple R_MIPS_LO16
+ * entries may be associated with a single R_MIPS_HI16. Use of these
+ * relocation types in a SHT_REL section is discouraged and may be
+ * forbidden to avoid this complication.
+ *
+ * A GNU extension allows multiple R_MIPS_HI16 records to share the same
+ * R_MIPS_LO16 relocation record(s). The association works like this within
+ * a single relocation section:
+ *
+ * + From the beginning of the section moving to the end of the section,
+ * until R_MIPS_LO16 is not found each found R_MIPS_HI16 relocation will
+ * be associated with the first R_MIPS_LO16.
+ *
+ * + Until another R_MIPS_HI16 record is found all found R_MIPS_LO16
+ * relocations found are associated with the last R_MIPS_HI16.
+ *
+ * ===
+ *
+ * This is so gcc can do dead code detection/removal without having to
+ * generate HI/LO pairs even if one of them would be deleted.
+ *
+ * So, the summary is:
+ *
+ * + A HI16 entry must occur before any LO16 entries;
+ * + Multiple consecutive HI16 RELA entries need to be buffered until the
+ * first LO16 RELA entry occurs - and then all HI16 RELA relocations use
+ * the offset in the LOW16 RELA for calculating their offsets;
+ * + The last HI16 RELA entry before a LO16 RELA entry is used (the AHL)
+ * for the first subsequent LO16 calculation;
+ * + If multiple consecutive LO16 RELA entries occur, only the first
+ * LO16 RELA entry triggers an update of buffered HI16 RELA entries;
+ * any subsequent LO16 RELA entry before another HI16 RELA entry will
+ * not cause any further updates to the HI16 RELA entries.
+ *
+ * Additionally, flush out any outstanding HI16 entries that don't have
+ * a LO16 entry in case some garbage entries are left in the file.
+ */
+
+struct mips_tmp_reloc;
+struct mips_tmp_reloc {
+ struct mips_tmp_reloc *next;
+
+ Elf_Addr ahl;
+ Elf32_Addr *where_hi16;
+};
+
+static struct mips_tmp_reloc *ml = NULL;
+
+/*
+ * Add a temporary relocation (ie, a HI16 reloc type.)
+ */
+static int
+mips_tmp_reloc_add(Elf_Addr ahl, Elf32_Addr *where_hi16)
+{
+ struct mips_tmp_reloc *r;
+
+ r = malloc(sizeof(struct mips_tmp_reloc), M_TEMP, M_NOWAIT);
+ if (r == NULL) {
+ printf("%s: failed to malloc\n", __func__);
+ return (0);
+ }
+
+ r->ahl = ahl;
+ r->where_hi16 = where_hi16;
+ r->next = ml;
+ ml = r;
+
+ return (1);
+}
+
+/*
+ * Flush the temporary relocation list.
+ *
+ * This should be done after a file is completely loaded
+ * so no stale relocations exist to confuse the next
+ * load.
+ */
+static void
+mips_tmp_reloc_flush(void)
+{
+ struct mips_tmp_reloc *r, *rn;
+
+ r = ml;
+ ml = NULL;
+ while (r != NULL) {
+ rn = r->next;
+ free(r, M_TEMP);
+ r = rn;
+ }
+}
+
+/*
+ * Get an entry from the reloc list; or NULL if we've run out.
+ */
+static struct mips_tmp_reloc *
+mips_tmp_reloc_get(void)
+{
+ struct mips_tmp_reloc *r;
+
+ r = ml;
+ if (r == NULL)
+ return (NULL);
+ ml = ml->next;
+ return (r);
+}
+
+/*
+ * Free a relocation entry.
+ */
+static void
+mips_tmp_reloc_free(struct mips_tmp_reloc *r)
+{
+
+ free(r, M_TEMP);
+}
+
/* Process one elf relocation with addend. */
static int
elf_reloc_internal(linker_file_t lf, Elf_Addr relocbase, const void *data,
@@ -170,15 +300,13 @@ elf_reloc_internal(linker_file_t lf, Elf_Addr relocbase, const void *data,
Elf_Addr addr;
Elf_Addr addend = (Elf_Addr)0;
Elf_Word rtype = (Elf_Word)0, symidx;
+ struct mips_tmp_reloc *r;
const Elf_Rel *rel = NULL;
const Elf_Rela *rela = NULL;
int error;
- /*
- * Stash R_MIPS_HI16 info so we can use it when processing R_MIPS_LO16
- */
- static Elf_Addr ahl;
- static Elf32_Addr *where_hi16;
+ /* Store the last seen ahl from a HI16 for LO16 processing */
+ static Elf_Addr last_ahl;
switch (type) {
case ELF_RELOC_REL:
@@ -248,6 +376,24 @@ elf_reloc_internal(linker_file_t lf, Elf_Addr relocbase, const void *data,
*(Elf64_Addr*)where = addr;
break;
+ /*
+ * Handle the two GNU extension cases:
+ *
+ * + Multiple HI16s followed by a LO16, and
+ * + A HI16 followed by multiple LO16s.
+ *
+ * The former is tricky - the HI16 relocations need
+ * to be buffered until a LO16 occurs, at which point
+ * each HI16 is replayed against the LO16 relocation entry
+ * (with the relevant overflow information.)
+ *
+ * The latter should be easy to handle - when the
+ * first LO16 is seen, write out and flush the
+ * HI16 buffer. Any subsequent LO16 entries will
+ * find a blank relocation buffer.
+ *
+ */
+
case R_MIPS_HI16: /* ((AHL + S) - ((short)(AHL + S)) >> 16 */
if (rela != NULL) {
error = lookup(lf, symidx, 1, &addr);
@@ -256,10 +402,24 @@ elf_reloc_internal(linker_file_t lf, Elf_Addr relocbase, const void *data,
addr += addend;
*where &= 0xffff0000;
*where |= ((((long long) addr + 0x8000LL) >> 16) & 0xffff);
- }
- else {
- ahl = addend << 16;
- where_hi16 = where;
+ } else {
+ /*
+ * Add a temporary relocation to the list;
+ * will pop it off / free the list when
+ * we've found a suitable HI16.
+ */
+ if (mips_tmp_reloc_add(addend << 16, where) == 0)
+ return (-1);
+ /*
+ * Track the last seen HI16 AHL for use by
+ * the first LO16 AHL calculation.
+ *
+ * The assumption is any intermediary deleted
+ * LO16's were optimised out, so the last
+ * HI16 before the LO16 is the "true" relocation
+ * entry to use for that LO16 write.
+ */
+ last_ahl = addend << 16;
}
break;
@@ -271,21 +431,48 @@ elf_reloc_internal(linker_file_t lf, Elf_Addr relocbase, const void *data,
addr += addend;
*where &= 0xffff0000;
*where |= addr & 0xffff;
- }
- else {
- ahl += (int16_t)addend;
+ } else {
+ Elf_Addr tmp_ahl;
+ Elf_Addr tmp_addend;
+
+ tmp_ahl = last_ahl + (int16_t) addend;
error = lookup(lf, symidx, 1, &addr);
if (error != 0)
return (-1);
- addend &= 0xffff0000;
- addend |= (uint16_t)(ahl + addr);
- *where = addend;
-
- addend = *where_hi16;
- addend &= 0xffff0000;
- addend |= ((ahl + addr) - (int16_t)(ahl + addr)) >> 16;
- *where_hi16 = addend;
+ tmp_addend = addend & 0xffff0000;
+
+ /* Use the last seen ahl for calculating addend */
+ tmp_addend |= (uint16_t)(tmp_ahl + addr);
+ *where = tmp_addend;
+
+ /*
+ * This logic implements the "we saw multiple HI16
+ * before a LO16" assignment /and/ "we saw multiple
+ * LO16s".
+ *
+ * Multiple LO16s will be handled as a blank
+ * relocation list.
+ *
+ * Multple HI16's are iterated over here.
+ */
+ while ((r = mips_tmp_reloc_get()) != NULL) {
+ Elf_Addr rahl;
+
+ /*
+ * We have the ahl from the HI16 entry, so
+ * offset it by the 16 bits of the low ahl.
+ */
+ rahl = r->ahl;
+ rahl += (int16_t) addend;
+
+ tmp_addend = *(r->where_hi16);
+ tmp_addend &= 0xffff0000;
+ tmp_addend |= ((rahl + addr) -
+ (int16_t)(rahl + addr)) >> 16;
+ *(r->where_hi16) = tmp_addend;
+ mips_tmp_reloc_free(r);
+ }
}
break;
@@ -342,6 +529,9 @@ elf_cpu_load_file(linker_file_t lf __unused)
*/
mips_icache_sync_all();
+ /* Flush outstanding relocations */
+ mips_tmp_reloc_flush();
+
return (0);
}