riscv/arch_elf.c: Handle PCREL_HI20/LO12_I/S relocations correctly

There is a problem with the current elf loader for risc-v: when a pair of
PCREL_HI20 / LO12 relocations are encountered, it is assumed that these
will follow each other immediately, as follows:

label:
	auipc      a0, %pcrel_hi(symbol)    // R_RISCV_PCREL_HI20
	load/store a0, %pcrel_lo(label)(a0) // R_RISCV_PCREL_LO12_I/S

With this assumption, the hi/lo relocations are both done when a hi20
relocation entry is encountered, first to the current instruction (addr)
and to the next instruction (addr + 4).

However, this assumption is wrong. There is nothing in the elf relocation
specification[1] that mandates this. Thus, the hi/lo relocation always
needs to first fixup the hi-part, and when the lo-part is encountered, it
needs to find the corresponding hi relocation entry, via the given "label".
This necessitates (re-)visiting the relocation entries for the current
section as well as looking for "label" in the symbol table.

The NuttX elf loader does not allow such operations to be done in the
machine specific part, so this patch fixes the relocation issue by
introducing an architecture specific cache for the hi20 relocation and
symbol table entries. When a lo12 relocation is encountered, the cache
can be consulted to find the hi20 part.

[1] https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/master/riscv-elf.adoc
This commit is contained in:
Ville Juven
2023-12-05 12:30:46 +02:00
committed by Xiang Xiao
parent 267d039295
commit 996625ec58
17 changed files with 249 additions and 57 deletions
+147 -25
View File
@@ -178,6 +178,84 @@ static void _calc_imm(long offset, long *imm_hi, long *imm_lo)
*imm_hi = hi;
}
/****************************************************************************
* Name: _add_hi20
*
* Description:
* Add PCREL_HI20 relocation offset to the LUT. When a PCREL_LO12_I/_S is
* encountered, the corresponding PCREL_HI20 value can be found from it.
*
* Input Parameters:
* arch_data - Where the PCREL_HI20 relocations are listed.
* hi20_rel - The PCREL_HI20 relocation entry.
* hi20_offset - The corresponding offset value.
*
* Returned Value:
* None.
*
****************************************************************************/
static void _add_hi20(void *arch_data, uintptr_t hi20_rel,
uintptr_t hi20_offset)
{
arch_elfdata_t *data = (arch_elfdata_t *)arch_data;
int i;
/* Try to find a free slot from the list */
for (i = 0; i < ARCH_ELF_RELCNT; i++)
{
struct hi20_rels_s *hi20 = &data->hi20_rels[i];
if (hi20->hi20_rel == 0)
{
hi20->hi20_rel = hi20_rel;
hi20->hi20_offset = hi20_offset;
break;
}
}
}
/****************************************************************************
* Name: _find_hi20
*
* Description:
* Find PCREL_HI20 relocation offset from the LUT. When a PCREL_LO12_I/_S
* is encountered, the corresponding PCREL_HI20 value is needed to do the
* relocation.
*
* Input Parameters:
* arch_data - Where the PCREL_HI20 relocations are listed.
* hi20_rel - The PCREL_HI20 relocation entry.
*
* Returned Value:
* The corresponding hi20_offset value.
*
****************************************************************************/
static uintptr_t _find_hi20(void *arch_data, uintptr_t hi20_rel)
{
arch_elfdata_t *data = (arch_elfdata_t *)arch_data;
int i;
/* Try to find the hi20 value from the list */
for (i = 0; i < ARCH_ELF_RELCNT; i++)
{
struct hi20_rels_s *hi20 = &data->hi20_rels[i];
if (hi20->hi20_rel == hi20_rel)
{
/* Found it, we can clear the entry now */
hi20->hi20_rel = 0;
return hi20->hi20_offset;
}
}
return 0;
}
/****************************************************************************
* Public Functions
****************************************************************************/
@@ -266,14 +344,15 @@ bool up_checkarch(const Elf_Ehdr *ehdr)
*
****************************************************************************/
int up_relocate(const Elf_Rel *rel, const Elf_Sym *sym, uintptr_t addr)
int up_relocate(const Elf_Rel *rel, const Elf_Sym *sym, uintptr_t addr,
void *arch_data)
{
berr("Not implemented\n");
return -ENOSYS;
}
int up_relocateadd(const Elf_Rela *rel, const Elf_Sym *sym,
uintptr_t addr)
uintptr_t addr, void *arch_data)
{
long offset;
unsigned int relotype;
@@ -317,22 +396,58 @@ int up_relocateadd(const Elf_Rela *rel, const Elf_Sym *sym,
break;
case R_RISCV_PCREL_LO12_I:
case R_RISCV_PCREL_LO12_S:
{
long imm_hi;
long imm_lo;
binfo("%s at %08" PRIxPTR " [%08" PRIx32 "] "
"to sym=%p st_value=%08lx\n",
_get_rname(relotype),
addr, _get_val((uint16_t *)addr),
sym, sym->st_value);
/* NOTE: imm value for mv has been adjusted in previous HI20 */
offset = _find_hi20(arch_data, sym->st_value);
/* Adjust imm for MV(ADDI) / JR (JALR) : I-type */
_calc_imm(offset, &imm_hi, &imm_lo);
_add_val((uint16_t *)addr, (int32_t)imm_lo << 20);
}
break;
case R_RISCV_PCREL_LO12_S:
{
uint32_t val;
long imm_hi;
long imm_lo;
binfo("%s at %08" PRIxPTR " [%08" PRIx32 "] "
"to sym=%p st_value=%08lx\n",
_get_rname(relotype),
addr, _get_val((uint16_t *)addr),
sym, sym->st_value);
offset = _find_hi20(arch_data, sym->st_value);
/* Adjust imm for SW : S-type */
_calc_imm(offset, &imm_hi, &imm_lo);
val = (((int32_t)imm_lo >> 5) << 25) +
(((int32_t)imm_lo & 0x1f) << 7);
binfo("imm_lo=%ld (%lx), val=%" PRIx32 "\n", imm_lo, imm_lo, val);
_add_val((uint16_t *)addr, val);
}
break;
case R_RISCV_PCREL_HI20:
case R_RISCV_CALL:
case R_RISCV_CALL_PLT:
{
long imm_hi;
long imm_lo;
binfo("%s at %08" PRIxPTR " [%08" PRIx32 "] "
"to sym=%p st_value=%08lx\n",
_get_rname(relotype),
@@ -341,34 +456,41 @@ int up_relocateadd(const Elf_Rela *rel, const Elf_Sym *sym,
offset = (long)sym->st_value + (long)rel->r_addend - (long)addr;
_calc_imm(offset, &imm_hi, &imm_lo);
/* Adjust auipc (add upper immediate to pc) : 20bit */
_add_val((uint16_t *)addr, imm_hi << 12);
/* Add the hi20 value to the cache */
_add_hi20(arch_data, addr, offset);
}
break;
case R_RISCV_CALL:
case R_RISCV_CALL_PLT:
{
long imm_hi;
long imm_lo;
binfo("%s at %08" PRIxPTR " [%08" PRIx32 "] "
"to sym=%p st_value=%08lx\n",
_get_rname(relotype),
addr, _get_val((uint16_t *)addr),
sym, sym->st_value);
offset = (long)sym->st_value + (long)rel->r_addend - (long)addr;
_calc_imm(offset, &imm_hi, &imm_lo);
/* Adjust auipc (add upper immediate to pc) : 20bit */
_add_val((uint16_t *)addr, (imm_hi << 12));
_add_val((uint16_t *)addr, imm_hi << 12);
if ((_get_val((uint16_t *)(addr + 4)) & 0x7f) == OPCODE_SW)
{
/* Adjust imm for SW : S-type */
/* Adjust imm for CALL (JALR) : I-type */
uint32_t val =
(((int32_t)imm_lo >> 5) << 25) +
(((int32_t)imm_lo & 0x1f) << 7);
binfo("imm_lo=%ld (%lx), val=%" PRIx32 "\n",
imm_lo, imm_lo, val);
_add_val((uint16_t *)(addr + 4), val);
}
else
{
/* Adjust imm for MV(ADDI)/JALR : I-type */
_add_val((uint16_t *)(addr + 4), ((int32_t)imm_lo << 20));
}
_add_val((uint16_t *)(addr + 4), (int32_t)imm_lo << 20);
}
break;