nuttx/libs/libc/machine/arm64/arch_elf.c

800 lines
20 KiB
C
Raw Normal View History

/****************************************************************************
* libs/libc/machine/arm64/arch_elf.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <inttypes.h>
#include <stdlib.h>
#include <errno.h>
#include <debug.h>
#include <endian.h>
#include <nuttx/compiler.h>
#include <nuttx/bits.h>
#include <nuttx/elf.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* For triggering a fault on purpose (reserved) */
#define FAULT_BRK_IMM 0x100
/* BRK instruction encoding
* The #imm16 value should be placed at bits[20:5] within BRK ins
*/
#define AARCH64_BREAK_MON 0xd4200000
/* BRK instruction for provoking a fault on purpose
* Unlike kgdb, #imm16 value with unallocated handler is used for faulting.
*/
#define AARCH64_BREAK_FAULT (AARCH64_BREAK_MON | (FAULT_BRK_IMM << 5))
#define ADR_IMM_HILOSPLIT 2
#define ADR_IMM_SIZE (2 * 1024 * 1024)
#define ADR_IMM_LOMASK ((1 << ADR_IMM_HILOSPLIT) - 1)
#define ADR_IMM_HIMASK ((ADR_IMM_SIZE >> ADR_IMM_HILOSPLIT) - 1)
#define ADR_IMM_LOSHIFT 29
#define ADR_IMM_HISHIFT 5
#define INSN_SF_BIT BIT(31)
#define INSN_N_BIT BIT(22)
#define INSN_LSL_12 BIT(22)
/****************************************************************************
* Private Types
****************************************************************************/
enum reloc_op_e
{
RELOC_OP_NONE,
RELOC_OP_ABS,
RELOC_OP_PREL,
RELOC_OP_PAGE,
};
enum insn_movw_imm_type_e
{
INSN_IMM_MOVNZ,
INSN_IMM_MOVKZ,
};
enum insn_imm_type_e
{
INSN_IMM_ADR,
INSN_IMM_26,
INSN_IMM_19,
INSN_IMM_16,
INSN_IMM_14,
INSN_IMM_12,
INSN_IMM_N,
INSN_IMM_MAX
};
/****************************************************************************
* Private Functions
****************************************************************************/
static uint32_t
aarch64_insn_encode_immediate(enum insn_imm_type_e type,
uint32_t insn, uint64_t imm)
{
uint32_t immlo;
uint32_t immhi;
uint32_t mask;
int shift;
if (insn == AARCH64_BREAK_FAULT)
{
return AARCH64_BREAK_FAULT;
}
switch (type)
{
case INSN_IMM_ADR:
{
shift = 0;
immlo = (imm & ADR_IMM_LOMASK) << ADR_IMM_LOSHIFT;
imm >>= ADR_IMM_HILOSPLIT;
immhi = (imm & ADR_IMM_HIMASK) << ADR_IMM_HISHIFT;
imm = immlo | immhi;
mask = (ADR_IMM_LOMASK << ADR_IMM_LOSHIFT) |
(ADR_IMM_HIMASK << ADR_IMM_HISHIFT);
}
break;
case INSN_IMM_26:
{
mask = BIT(26) - 1;
shift = 0;
}
break;
case INSN_IMM_19:
{
mask = BIT(19) - 1;
shift = 5;
}
break;
case INSN_IMM_16:
{
mask = BIT(16) - 1;
shift = 5;
}
break;
case INSN_IMM_14:
{
mask = BIT(14) - 1;
shift = 5;
}
break;
case INSN_IMM_12:
{
mask = BIT(12) - 1;
shift = 10;
}
break;
default:
{
berr("unknown immediate encoding %d\n", type);
return AARCH64_BREAK_FAULT;
}
}
/* Update the immediate field. */
insn &= ~(mask << shift);
insn |= (imm & mask) << shift;
return insn;
}
static uint64_t do_reloc(enum reloc_op_e op,
uintptr_t place, uint64_t val)
{
switch (op)
{
case RELOC_OP_ABS:
return val;
case RELOC_OP_PREL:
return val - (uint64_t)place;
case RELOC_OP_PAGE:
return (val & ~0xfff) - ((uint64_t)place & ~0xfff);
case RELOC_OP_NONE:
return 0;
}
return 0;
}
static int reloc_data(enum reloc_op_e op, uintptr_t place,
uint64_t val, int len)
{
int64_t sval = do_reloc(op, place, val);
/* The ELF psABI for AArch64 documents the 16-bit and 32-bit place
* relative and absolute relocations as having a range of [-2^15, 2^16)
* or [-2^31, 2^32), respectively. However, in order to be able to
* detect overflows reliably, we have to choose whether we interpret
* such quantities as signed or as unsigned, and stick with it.
* The way we organize our address space requires a signed
* interpretation of 32-bit relative references, so let's use that
* for all R_AARCH64_PRELxx relocations. This means our upper
* bound for overflow detection should be Sxx_MAX rather than Uxx_MAX.
*/
switch (len)
{
case 16:
{
*(int16_t *)place = sval;
switch (op)
{
case RELOC_OP_ABS:
{
if (sval < 0 || sval > UINT16_MAX)
{
return -ERANGE;
}
}
break;
case RELOC_OP_PREL:
{
if (sval < INT16_MIN || sval > INT16_MAX)
{
return -ERANGE;
}
}
break;
default:
{
berr("Invalid 16-bit data relocation (%d)\n", op);
return -EINVAL;
}
}
}
break;
case 32:
{
*(int32_t *)place = sval;
switch (op)
{
case RELOC_OP_ABS:
{
if (sval < 0 || sval > UINT32_MAX)
{
return -ERANGE;
}
}
break;
case RELOC_OP_PREL:
{
if (sval < INT32_MIN || sval > INT32_MAX)
{
return -ERANGE;
}
}
break;
default:
{
berr("Invalid 32-bit data relocation (%d)\n", op);
return -EINVAL;
}
}
}
break;
case 64:
{
*(int64_t *)place = sval;
}
break;
default:
{
berr("Invalid length (%d) for data relocation\n", len);
return -EINVAL;
}
}
return 0;
}
static int reloc_insn_movw(enum reloc_op_e op, uintptr_t place,
uint64_t val, int lsb,
enum insn_movw_imm_type_e imm_type)
{
uint32_t insn = htole32(*(uint32_t *)place);
uint64_t imm;
int64_t sval;
sval = do_reloc(op, place, val);
imm = sval >> lsb;
if (imm_type == INSN_IMM_MOVNZ)
{
/* For signed MOVW relocations, we have to manipulate the
* instruction encoding depending on whether or not the
* immediate is less than zero.
*/
insn &= ~(3 << 29);
if (sval >= 0)
{
/* >=0: Set the instruction to MOVZ (opcode 10b). */
insn |= 2 << 29;
}
else
{
/* <0: Set the instruction to MOVN (opcode 00b).
* Since we've masked the opcode already, we
* don't need to do anything other than
* inverting the new immediate field.
*/
imm = ~imm;
}
}
/* Update the instruction with the new encoding. */
insn = aarch64_insn_encode_immediate(INSN_IMM_16, insn, imm);
*(uint32_t *)place = le32toh(insn);
if (imm > UINT16_MAX)
{
return -ERANGE;
}
return 0;
}
static int reloc_insn_imm(enum reloc_op_e op, uintptr_t place,
uint64_t val, int lsb, int len,
enum insn_imm_type_e imm_type)
{
int64_t sval;
uint64_t imm;
uint64_t imm_mask;
uint32_t insn = le32toh(*(uint32_t *)place);
/* Calculate the relocation value. */
sval = do_reloc(op, place, val);
sval >>= lsb;
/* Extract the value bits and shift them to bit 0. */
imm_mask = (BIT(lsb + len) - 1) >> lsb;
imm = sval & imm_mask;
/* Update the instruction's immediate field. */
insn = aarch64_insn_encode_immediate(imm_type, insn, imm);
*(uint32_t *)place = htole32(insn);
/* Extract the upper value bits (including the sign bit) and
* shift them to bit 0.
*/
sval = (int64_t)(sval & ~(imm_mask >> 1)) >> (len - 1);
/* Overflow has occurred if the upper bits are not all equal to
* the sign bit of the value.
*/
if ((uint64_t)(sval + 1) >= 2)
{
return -ERANGE;
}
return 0;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: up_checkarch
*
* Description:
* Given the ELF header in 'hdr', verify that the ELF file is appropriate
* for the current, configured architecture. Every architecture that uses
* the ELF loader must provide this function.
*
* Input Parameters:
* hdr - The ELF header read from the ELF file.
*
* Returned Value:
* True if the architecture supports this ELF file.
*
****************************************************************************/
bool up_checkarch(const Elf64_Ehdr *ehdr)
{
/* Make sure it's an ARM executable */
if (ehdr->e_machine != EM_AARCH64)
{
berr("ERROR: Not for AARCH64: e_machine=%04x\n", ehdr->e_machine);
return false;
}
/* Make sure that 64-bit objects are supported */
if (ehdr->e_ident[EI_CLASS] != ELFCLASS64)
{
berr("ERROR: Need 64-bit objects: e_ident[EI_CLASS]=%02x\n",
ehdr->e_ident[EI_CLASS]);
return false;
}
/* Verify endian-ness */
#ifdef CONFIG_ENDIAN_BIG
if (ehdr->e_ident[EI_DATA] != ELFDATA2MSB)
#else
if (ehdr->e_ident[EI_DATA] != ELFDATA2LSB)
#endif
{
berr("ERROR: Wrong endian-ness: e_ident[EI_DATA]=%02x\n",
ehdr->e_ident[EI_DATA]);
return false;
}
/* TODO: Check ABI here. */
return true;
}
/****************************************************************************
* Name: up_relocate and up_relocateadd
*
* Description:
* Perform an architecture-specific ELF relocation. Every architecture
* that uses the ELF loader must provide this function.
*
* Input Parameters:
* rel - The relocation type
* sym - The ELF symbol structure containing the fully resolved value.
* There are a few relocation types for a few architectures that do
* not require symbol information. For those, this value will be
* NULL. Implementations of these functions must be able to handle
* that case.
* addr - The address that requires the relocation.
*
* Returned Value:
* Zero (OK) if the relocation was successful. Otherwise, a negated errno
* value indicating the cause of the relocation failure.
*
****************************************************************************/
int up_relocate(const Elf64_Rel *rel, const Elf64_Sym *sym, uintptr_t addr,
void *arch_data)
{
berr("ERROR: REL relocation not supported\n");
return -ENOSYS;
}
int up_relocateadd(const Elf64_Rela *rel, const Elf64_Sym *sym,
uintptr_t addr, void *arch_data)
{
bool overflow_check = true;
uint64_t val;
int ret = 0;
/* addr corresponds to P in the AArch64 ELF document. */
/* val corresponds to (S + A) in the AArch64 ELF document. */
val = sym->st_value + rel->r_addend;
/* Handle the relocation by relocation type */
switch (ELF64_R_TYPE(rel->r_info))
{
case R_AARCH64_NONE:
{
/* No relocation */
}
break;
/* Data relocations */
case R_AARCH64_ABS64:
{
overflow_check = false;
ret = reloc_data(RELOC_OP_ABS, addr, val, 64);
}
break;
case R_AARCH64_ABS32:
{
ret = reloc_data(RELOC_OP_ABS, addr, val, 32);
}
break;
case R_AARCH64_ABS16:
{
ret = reloc_data(RELOC_OP_ABS, addr, val, 16);
}
break;
case R_AARCH64_PREL64:
{
overflow_check = false;
ret = reloc_data(RELOC_OP_PREL, addr, val, 64);
}
break;
case R_AARCH64_PREL32:
{
ret = reloc_data(RELOC_OP_PREL, addr, val, 32);
}
break;
case R_AARCH64_PREL16:
{
ret = reloc_data(RELOC_OP_PREL, addr, val, 16);
}
break;
case R_AARCH64_MOVW_UABS_G0_NC:
{
overflow_check = false;
}
/* fallthrough */
case R_AARCH64_MOVW_UABS_G0:
{
ret = reloc_insn_movw(RELOC_OP_ABS, addr, val, 0,
INSN_IMM_MOVKZ);
}
break;
case R_AARCH64_MOVW_UABS_G1_NC:
{
overflow_check = false;
}
/* fallthrough */
case R_AARCH64_MOVW_UABS_G1:
{
ret = reloc_insn_movw(RELOC_OP_ABS, addr, val, 16,
INSN_IMM_MOVKZ);
}
break;
case R_AARCH64_MOVW_UABS_G2_NC:
{
overflow_check = false;
}
/* fallthrough */
case R_AARCH64_MOVW_UABS_G2:
{
ret = reloc_insn_movw(RELOC_OP_ABS, addr, val, 32,
INSN_IMM_MOVKZ);
}
break;
case R_AARCH64_MOVW_UABS_G3:
{
/* We're using the top bits so we can't overflow. */
overflow_check = false;
ret = reloc_insn_movw(RELOC_OP_ABS, addr, val, 48,
INSN_IMM_MOVKZ);
}
break;
case R_AARCH64_MOVW_SABS_G0:
{
ret = reloc_insn_movw(RELOC_OP_ABS, addr, val, 0,
INSN_IMM_MOVNZ);
}
break;
case R_AARCH64_MOVW_SABS_G1:
{
ret = reloc_insn_movw(RELOC_OP_ABS, addr, val, 16,
INSN_IMM_MOVNZ);
}
break;
case R_AARCH64_MOVW_SABS_G2:
{
ret = reloc_insn_movw(RELOC_OP_ABS, addr, val, 32,
INSN_IMM_MOVNZ);
}
break;
case R_AARCH64_MOVW_PREL_G0_NC:
{
overflow_check = false;
ret = reloc_insn_movw(RELOC_OP_PREL, addr, val, 0,
INSN_IMM_MOVKZ);
}
break;
case R_AARCH64_MOVW_PREL_G0:
{
ret = reloc_insn_movw(RELOC_OP_PREL, addr, val, 0,
INSN_IMM_MOVNZ);
}
break;
case R_AARCH64_MOVW_PREL_G1_NC:
{
overflow_check = false;
ret = reloc_insn_movw(RELOC_OP_PREL, addr, val, 16,
INSN_IMM_MOVKZ);
}
break;
case R_AARCH64_MOVW_PREL_G1:
{
ret = reloc_insn_movw(RELOC_OP_PREL, addr, val, 16,
INSN_IMM_MOVNZ);
}
break;
case R_AARCH64_MOVW_PREL_G2_NC:
{
overflow_check = false;
ret = reloc_insn_movw(RELOC_OP_PREL, addr, val, 32,
INSN_IMM_MOVKZ);
}
break;
case R_AARCH64_MOVW_PREL_G2:
{
ret = reloc_insn_movw(RELOC_OP_PREL, addr, val, 32,
INSN_IMM_MOVNZ);
}
break;
case R_AARCH64_MOVW_PREL_G3:
{
/* We're using the top bits so we can't overflow. */
overflow_check = false;
ret = reloc_insn_movw(RELOC_OP_PREL, addr, val, 48,
INSN_IMM_MOVNZ);
}
break;
/* Immediate instruction relocations. */
case R_AARCH64_LD_PREL_LO19:
{
ret = reloc_insn_imm(RELOC_OP_PREL, addr, val, 2, 19,
INSN_IMM_19);
}
break;
case R_AARCH64_ADR_PREL_LO21:
{
ret = reloc_insn_imm(RELOC_OP_PREL, addr, val, 0, 21,
INSN_IMM_ADR);
}
break;
case R_AARCH64_ADR_PREL_PG_HI21_NC:
{
overflow_check = false;
}
/* fallthrough */
case R_AARCH64_ADR_PREL_PG_HI21:
{
if (((uint64_t)addr & 0xfff) < 0xff8)
{
ret = reloc_insn_imm(RELOC_OP_PAGE, addr, val, 12, 21,
INSN_IMM_ADR);
}
else
{
uint32_t insn;
/* patch ADRP to ADR if it is in range */
ret = reloc_insn_imm(RELOC_OP_PREL, addr, val & ~0xfff, 0, 21,
INSN_IMM_ADR);
if (ret == 0)
{
insn = le32toh(*(uint32_t *)addr);
insn &= ~BIT(31);
*(uint32_t *)addr = htole32(insn);
}
else
{
berr("Out of range for ADR\n");
return -EINVAL;
}
}
}
break;
case R_AARCH64_ADD_ABS_LO12_NC:
case R_AARCH64_LDST8_ABS_LO12_NC:
{
overflow_check = false;
ret = reloc_insn_imm(RELOC_OP_ABS, addr, val, 0, 12,
INSN_IMM_12);
}
break;
case R_AARCH64_LDST16_ABS_LO12_NC:
{
overflow_check = false;
ret = reloc_insn_imm(RELOC_OP_ABS, addr, val, 1, 11,
INSN_IMM_12);
}
break;
case R_AARCH64_LDST32_ABS_LO12_NC:
{
overflow_check = false;
ret = reloc_insn_imm(RELOC_OP_ABS, addr, val, 2, 10,
INSN_IMM_12);
}
break;
case R_AARCH64_LDST64_ABS_LO12_NC:
{
overflow_check = false;
ret = reloc_insn_imm(RELOC_OP_ABS, addr, val, 3, 9,
INSN_IMM_12);
}
break;
case R_AARCH64_LDST128_ABS_LO12_NC:
{
overflow_check = false;
ret = reloc_insn_imm(RELOC_OP_ABS, addr, val, 4, 8,
INSN_IMM_12);
}
break;
case R_AARCH64_TSTBR14:
{
ret = reloc_insn_imm(RELOC_OP_PREL, addr, val, 2, 14,
INSN_IMM_14);
}
break;
case R_AARCH64_CONDBR19:
{
ret = reloc_insn_imm(RELOC_OP_PREL, addr, val, 2, 19,
INSN_IMM_19);
}
break;
case R_AARCH64_JUMP26:
case R_AARCH64_CALL26:
{
ret = reloc_insn_imm(RELOC_OP_PREL, addr, val, 2, 26,
INSN_IMM_26);
}
break;
default:
berr("ERROR: Unsupported relocation: %"PRIu64"\n",
ELF64_R_TYPE(rel->r_info));
return -EINVAL;
}
if (overflow_check && ret == -ERANGE)
{
goto overflow;
}
return OK;
overflow:
berr("ERROR: overflow in relocation type %"PRIu64" val %"PRIu64"\n",
ELF64_R_TYPE(rel->r_info), val);
return -ENOEXEC;
}