Index: sys/mips/mips/elf_machdep.c =================================================================== --- sys/mips/mips/elf_machdep.c (revision 304201) +++ sys/mips/mips/elf_machdep.c (working copy) @@ -161,6 +161,112 @@ } #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. + */ + +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 + * + * XXX TODO: this should be done after a file is completely loaded, + * so no stale temp addresses exist. + */ +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 +276,13 @@ 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 +352,24 @@ *(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,11 +378,25 @@ addr += addend; *where &= 0xffff0000; *where |= ((((long long) addr + 0x8000LL) >> 16) & 0xffff); + } 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; } - else { - ahl = addend << 16; - where_hi16 = where; - } break; case R_MIPS_LO16: /* AHL + S */ @@ -271,21 +407,48 @@ 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; + tmp_addend = addend & 0xffff0000; - addend = *where_hi16; - addend &= 0xffff0000; - addend |= ((ahl + addr) - (int16_t)(ahl + addr)) >> 16; - *where_hi16 = addend; + /* 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 +505,9 @@ */ mips_icache_sync_all(); + /* Flush outstanding relocations */ + mips_tmp_reloc_flush(); + return (0); }