fix(riscv64-link): pair pcrel lo relocations by hi address

Track R_RISCV_PCREL_HI20/GOT_HI20 relocations by address and resolve LO12 relocations against the referenced HI, instead of only using the last seen HI.

Also reset/free the per-link relocation map in TCCState.
This commit is contained in:
diannaaa
2026-02-23 22:26:06 +08:00
parent 4597a9621e
commit fada98b1ce
3 changed files with 51 additions and 10 deletions

View File

@@ -173,6 +173,41 @@ ST_FUNC void relocate_plt(TCCState *s1)
}
}
static void riscv64_record_pcrel_hi(TCCState *s1, addr_t addr, addr_t val)
{
int n = s1->nb_pcrel_hi_entries;
if (n >= s1->alloc_pcrel_hi_entries) {
int new_alloc = s1->alloc_pcrel_hi_entries ? s1->alloc_pcrel_hi_entries * 2 : 64;
s1->pcrel_hi_entries = tcc_realloc(s1->pcrel_hi_entries,
new_alloc * sizeof(*s1->pcrel_hi_entries));
s1->alloc_pcrel_hi_entries = new_alloc;
}
s1->pcrel_hi_entries[n].addr = addr;
s1->pcrel_hi_entries[n].val = val;
s1->nb_pcrel_hi_entries = n + 1;
last_hi.addr = addr;
last_hi.val = val;
}
static int riscv64_lookup_pcrel_hi(TCCState *s1, addr_t hi_addr, addr_t *hi_val)
{
int i;
struct pcrel_hi *entry;
if (s1->nb_pcrel_hi_entries && hi_addr == last_hi.addr) {
*hi_val = last_hi.val;
return 1;
}
for (i = s1->nb_pcrel_hi_entries - 1; i >= 0; --i) {
entry = &s1->pcrel_hi_entries[i];
if (entry->addr == hi_addr) {
last_hi = *entry;
*hi_val = entry->val;
return 1;
}
}
return 0;
}
ST_FUNC void relocate(TCCState *s1, ElfW_Rel *rel, int type, unsigned char *ptr,
addr_t addr, addr_t val)
{
@@ -228,35 +263,31 @@ ST_FUNC void relocate(TCCState *s1, ElfW_Rel *rel, int type, unsigned char *ptr,
symtab_section->link->data + sym->st_name);
write32le(ptr, (read32le(ptr) & 0xfff)
| ((off64 & 0xfffff) << 12));
last_hi.addr = addr;
last_hi.val = val;
riscv64_record_pcrel_hi(s1, addr, val);
return;
case R_RISCV_GOT_HI20:
val = s1->got->sh_addr + get_sym_attr(s1, sym_index, 0)->got_offset;
off64 = (int64_t)(val - addr + 0x800) >> 12;
if ((off64 + ((uint64_t)1 << 20)) >> 21)
tcc_error_noabort("R_RISCV_GOT_HI20 relocation failed");
last_hi.addr = addr;
last_hi.val = val;
write32le(ptr, (read32le(ptr) & 0xfff)
| ((off64 & 0xfffff) << 12));
riscv64_record_pcrel_hi(s1, addr, val);
return;
case R_RISCV_PCREL_LO12_I:
#ifdef DEBUG_RELOC
printf("PCREL_LO12_I: val=%lx addr=%lx\n", (long)val, (long)addr);
#endif
if (val != last_hi.addr)
addr = val;
if (!riscv64_lookup_pcrel_hi(s1, addr, &val))
tcc_error_noabort("unsupported hi/lo pcrel reloc scheme");
val = last_hi.val;
addr = last_hi.addr;
write32le(ptr, (read32le(ptr) & 0xfffff)
| (((val - addr) & 0xfff) << 20));
return;
case R_RISCV_PCREL_LO12_S:
if (val != last_hi.addr)
addr = val;
if (!riscv64_lookup_pcrel_hi(s1, addr, &val))
tcc_error_noabort("unsupported hi/lo pcrel reloc scheme");
val = last_hi.val;
addr = last_hi.addr;
off32 = val - addr;
write32le(ptr, (read32le(ptr) & ~0xfe000f80)
| ((off32 & 0xfe0) << 20)

3
tcc.h
View File

@@ -939,6 +939,9 @@ struct TCCState {
#ifdef TCC_TARGET_RISCV64
struct pcrel_hi { addr_t addr, val; } last_hi;
struct pcrel_hi *pcrel_hi_entries;
int nb_pcrel_hi_entries;
int alloc_pcrel_hi_entries;
#define last_hi s1->last_hi
#endif

View File

@@ -145,6 +145,9 @@ ST_FUNC void tccelf_delete(TCCState *s1)
dynarray_reset(&s1->priv_sections, &s1->nb_priv_sections);
tcc_free(s1->sym_attrs);
#ifdef TCC_TARGET_RISCV64
tcc_free(s1->pcrel_hi_entries);
#endif
symtab_section = NULL; /* for tccrun.c:rt_printline() */
}
@@ -1127,6 +1130,10 @@ static void relocate_section(TCCState *s1, Section *s, Section *sr)
addr_t tgt, addr;
int is_dwarf = s->sh_num >= s1->dwlo && s->sh_num < s1->dwhi;
#ifdef TCC_TARGET_RISCV64
s1->nb_pcrel_hi_entries = 0;
#endif
qrel = (ElfW_Rel *)sr->data;
for_each_elem(sr, 0, rel, ElfW_Rel) {
if (s->data == NULL) /* bss */