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,83 @@ } #endif +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,6 +247,7 @@ 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; @@ -177,8 +255,12 @@ /* * Stash R_MIPS_HI16 info so we can use it when processing R_MIPS_LO16 */ +#if 0 static Elf_Addr ahl; static Elf32_Addr *where_hi16; +#endif + static Elf_Addr last_ahl; + static int seen_hi = -1, seen_lo = -1; switch (type) { case ELF_RELOC_REL: @@ -218,6 +300,7 @@ addr += addend; if (*where != addr) *where = (Elf32_Addr)addr; + seen_hi = 0; break; case R_MIPS_26: /* ((A << 2) | (P & 0xf0000000) + S) >> 2 */ @@ -237,6 +320,7 @@ *where &= ~0x03ffffff; *where |= addr & 0x03ffffff; + seen_hi = 0; break; case R_MIPS_64: /* S + A */ @@ -246,10 +330,29 @@ addr += addend; if (*(Elf64_Addr*)where != addr) *(Elf64_Addr*)where = addr; + seen_hi = 0; break; + /* + * Handle the two GNU extension cases: + * + * + Multiple HI16s followed by a LO16, and + * + A HI16 followed by multiple LO16s. + * + * The latter should be easy to handle - only do the + * write16 on the first (XXX TODO: or last?) LO16 + * before the next HI16. + * + * The former is harder - 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.) + */ + case R_MIPS_HI16: /* ((AHL + S) - ((short)(AHL + S)) >> 16 */ if (rela != NULL) { + if (seen_lo == 0) + printf("%s: called; where=%p; HI16 RELA with no LO16 first\n", __func__, where); error = lookup(lf, symidx, 1, &addr); if (error != 0) return (-1); @@ -256,15 +359,36 @@ addr += addend; *where &= 0xffff0000; *where |= ((((long long) addr + 0x8000LL) >> 16) & 0xffff); - } - else { + } else { + if (seen_lo == 0) + printf("%s: called; where=%p; HI16 REL with no LO16 first\n", __func__, where); + + /* + * 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); +#if 0 ahl = addend << 16; where_hi16 = where; +#endif + /* + * XXX TODO: for multiple HI before LO, should + * last_ahl be the /first/ HI16 seen, or the + * last HI16 seen? + */ + last_ahl = addend << 16; } + seen_hi = 1; + seen_lo = 0; break; case R_MIPS_LO16: /* AHL + S */ if (rela != NULL) { + if (seen_hi == 0) + printf("%s: called; where=%p; LO16 RELA with no HI16 first\n", __func__, where); error = lookup(lf, symidx, 1, &addr); if (error != 0) return (-1); @@ -271,22 +395,60 @@ addr += addend; *where &= 0xffff0000; *where |= addr & 0xffff; - } - else { + } else { + Elf_Addr tmp_ahl; + Elf_Addr tmp_addend; + /* No HI? Then we need separate logic */ + if (seen_hi == 0) + printf("%s: called; where=%p; LO16 REL with no HI16 first\n", __func__, where); + +#if 0 ahl += (int16_t)addend; +#endif + 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 */ + /* XXX TODO: is this right? */ + 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, and I + * think we have to offset it by the low + * 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); + } } + seen_hi = 0; + seen_lo = 1; break; @@ -342,6 +504,9 @@ */ mips_icache_sync_all(); + /* Flush outstanding relocations */ + mips_tmp_reloc_flush(); + return (0); }