mirror of https://github.com/deadw00d/AROS.git
1035 lines
32 KiB
C
1035 lines
32 KiB
C
/*
|
|
Copyright (C) 1995-2020, The AROS Development Team. All rights reserved.
|
|
$Id$
|
|
|
|
Desc: Code to dynamically load ELF executables
|
|
Lang: english
|
|
*/
|
|
|
|
#include <aros/debug.h>
|
|
|
|
#include <proto/dos.h>
|
|
#include <proto/arossupport.h>
|
|
#include <proto/debug.h>
|
|
#include <proto/exec.h>
|
|
|
|
#include <aros/asmcall.h>
|
|
#include <aros/macros.h>
|
|
#include <exec/memory.h>
|
|
#include <dos/elf.h>
|
|
#include <dos/dosasl.h>
|
|
#include <libraries/debug.h>
|
|
#include <resources/processor.h>
|
|
|
|
#include <string.h>
|
|
#include <stddef.h>
|
|
|
|
#include "internalloadseg.h"
|
|
#include "dos_intern.h"
|
|
#include "include/loadseg.h"
|
|
|
|
#define DATTR(x)
|
|
|
|
struct hunk
|
|
{
|
|
ULONG size;
|
|
BPTR next;
|
|
char data[0];
|
|
} __attribute__((packed));
|
|
|
|
#define BPTR2HUNK(bptr) ((struct hunk *)((void *)bptr - offsetof(struct hunk, next)))
|
|
#define HUNK2BPTR(hunk) MKBADDR(&hunk->next)
|
|
|
|
#define LOADSEG_SMALL_READ 2048 /* Size adjusted by profiling in Callgrind */
|
|
|
|
/* [S]mall [R]eads [Buffer]
|
|
*
|
|
* Due to a fact that Seek flushes buffers, FRead that is used to do small reads
|
|
* cannot use the buffer. The problem is visible with C++ executables that
|
|
* have a big number of small sections and thus do many (in tens of thousands
|
|
* for Odyssey) very small reads.
|
|
*
|
|
* */
|
|
struct SRBuffer
|
|
{
|
|
ULONG srb_FileOffset;
|
|
APTR srb_Buffer;
|
|
};
|
|
|
|
static int elf_read_block
|
|
(
|
|
BPTR file,
|
|
ULONG offset,
|
|
APTR buffer,
|
|
ULONG size,
|
|
SIPTR *funcarray,
|
|
struct SRBuffer *srb,
|
|
struct DosLibrary *DOSBase
|
|
)
|
|
{
|
|
D(bug("[ELF Loader] elf_read_block (offset=%d, size=%d)\n", offset, size));
|
|
|
|
if (size <= LOADSEG_SMALL_READ && (offset >= srb->srb_FileOffset) &&
|
|
((offset + size) <= (srb->srb_FileOffset + LOADSEG_SMALL_READ)) &&
|
|
srb->srb_Buffer != NULL)
|
|
{
|
|
CopyMem(srb->srb_Buffer + (offset - srb->srb_FileOffset), buffer, size);
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
if (ilsSeek(file, offset, OFFSET_BEGINNING) < 0)
|
|
return 1;
|
|
|
|
if (size <= LOADSEG_SMALL_READ)
|
|
{
|
|
if (srb->srb_Buffer == NULL)
|
|
srb->srb_Buffer = AllocMem(LOADSEG_SMALL_READ, MEMF_ANY);
|
|
|
|
srb->srb_FileOffset = offset;
|
|
|
|
/* Fill the buffer */
|
|
read_block(file, srb->srb_Buffer, LOADSEG_SMALL_READ, funcarray, DOSBase);
|
|
/* Re-read, now srb will be used */
|
|
return elf_read_block(file, offset, buffer, size, funcarray, srb, DOSBase);
|
|
}
|
|
else
|
|
{
|
|
return read_block(file, buffer, size, funcarray, DOSBase);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void *load_block
|
|
(
|
|
BPTR file,
|
|
ULONG offset,
|
|
ULONG size,
|
|
SIPTR *funcarray,
|
|
struct SRBuffer *srb,
|
|
struct DosLibrary *DOSBase
|
|
)
|
|
{
|
|
D(bug("[ELF Loader] Load Block\n"));
|
|
D(bug("[ELF Loader] (size=%d)\n",size));
|
|
D(bug("[ELF Loader] (funcarray=0x%x)\n",funcarray));
|
|
D(bug("[ELF Loader] (funcarray[1]=0x%x)\n",funcarray[1]));
|
|
void *block = ilsAllocMem(size, MEMF_ANY);
|
|
if (block)
|
|
{
|
|
if (!elf_read_block(file, offset, block, size, funcarray, srb, DOSBase))
|
|
return block;
|
|
|
|
ilsFreeMem(block, size);
|
|
}
|
|
else
|
|
SetIoErr(ERROR_NO_FREE_STORE);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static ULONG read_shnum(BPTR file, struct elfheader *eh, SIPTR *funcarray, struct SRBuffer *srb, struct DosLibrary *DOSBase)
|
|
{
|
|
ULONG shnum = eh->shnum;
|
|
|
|
/* the ELF header only uses 16 bits to store the count of section headers,
|
|
* so it can't handle more than 65535 headers. if the count is 0, and an
|
|
* offset is defined, then the real count can be found in the first
|
|
* section header (which always exists).
|
|
*
|
|
* similarly, if the string table index is SHN_XINDEX, then the actual
|
|
* index is found in the first section header also.
|
|
*
|
|
* see the System V ABI 2001-04-24 draft for more details.
|
|
*/
|
|
if (eh->shnum == 0)
|
|
{
|
|
struct sheader sh;
|
|
|
|
if (eh->shoff == 0) {
|
|
D(bug("[ELF Loader] No section header\n"));
|
|
SetIoErr(ERROR_NOT_EXECUTABLE);
|
|
return 0;
|
|
}
|
|
|
|
if (elf_read_block(file, eh->shoff, &sh, sizeof(sh), funcarray, srb, DOSBase))
|
|
return 0;
|
|
|
|
/* wider section header count is in the size field */
|
|
shnum = sh.size;
|
|
|
|
/* sanity, if they're still invalid then this isn't elf */
|
|
if (shnum == 0) {
|
|
D(bug("[ELF Loader] Empty section header\n"));
|
|
SetIoErr(ERROR_NOT_EXECUTABLE);
|
|
}
|
|
}
|
|
|
|
return shnum;
|
|
}
|
|
|
|
static int load_header(BPTR file, struct elfheader *eh, SIPTR *funcarray, struct SRBuffer *srb, struct DosLibrary *DOSBase)
|
|
{
|
|
if (elf_read_block(file, 0, eh, sizeof(struct elfheader), funcarray, srb, DOSBase))
|
|
return 0;
|
|
|
|
if (eh->ident[0] != 0x7f || eh->ident[1] != 'E' ||
|
|
eh->ident[2] != 'L' || eh->ident[3] != 'F') {
|
|
D(bug("[ELF Loader] Not an ELF object\n"));
|
|
SetIoErr(ERROR_NOT_EXECUTABLE);
|
|
return 0;
|
|
}
|
|
D(bug("[ELF Loader] ELF object\n"));
|
|
|
|
/* WANT_CLASS should be defined for your target */
|
|
if (eh->ident[EI_CLASS] != AROS_ELF_CLASS ||
|
|
eh->ident[EI_VERSION] != EV_CURRENT ||
|
|
eh->type != ET_REL ||
|
|
eh->ident[EI_DATA] != AROS_ELF_DATA ||
|
|
eh->machine != AROS_ELF_MACHINE)
|
|
{
|
|
D(bug("[ELF Loader] Object is of wrong type\n"));
|
|
D(bug("[ELF Loader] EI_CLASS is %d - should be %d\n", eh->ident[EI_CLASS] , AROS_ELF_CLASS ));
|
|
D(bug("[ELF Loader] EI_VERSION is %d - should be %d\n", eh->ident[EI_VERSION], EV_CURRENT ));
|
|
D(bug("[ELF Loader] type is %d - should be %d\n", eh->type , ET_REL ));
|
|
D(bug("[ELF Loader] EI_DATA is %d - should be %d\n", eh->ident[EI_DATA] , AROS_ELF_DATA ));
|
|
D(bug("[ELF Loader] machine is %d - should be %d\n", eh->machine , AROS_ELF_MACHINE));
|
|
|
|
SetIoErr(ERROR_NOT_EXECUTABLE);
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int __attribute__ ((noinline)) load_hunk
|
|
(
|
|
BPTR file,
|
|
BPTR **next_hunk_ptr,
|
|
struct sheader *sh,
|
|
CONST_STRPTR strtab,
|
|
SIPTR *funcarray,
|
|
BOOL do_align,
|
|
struct SRBuffer *srb,
|
|
struct DosLibrary *DOSBase
|
|
)
|
|
{
|
|
struct hunk *hunk;
|
|
ULONG hunk_size;
|
|
ULONG memflags = 0;
|
|
|
|
if (!sh->size)
|
|
return 1;
|
|
|
|
/* The size of the hunk is the size of the section, plus
|
|
the size of the hunk structure, plus the size of the alignment (if necessary)*/
|
|
hunk_size = sh->size + sizeof(struct hunk);
|
|
|
|
if (do_align)
|
|
{
|
|
hunk_size += sh->addralign;
|
|
|
|
/* Also create space for a trampoline, if necessary */
|
|
if (sh->flags & SHF_EXECINSTR)
|
|
hunk_size += sizeof(struct FullJumpVec);
|
|
}
|
|
|
|
if (strtab) {
|
|
CONST_STRPTR nameext;
|
|
|
|
nameext = strrchr(strtab + sh->name, '.');
|
|
if (nameext) {
|
|
if (strcmp(nameext, ".MEMF_CHIP")==0) {
|
|
memflags |= MEMF_CHIP;
|
|
} else if (strcmp(nameext, ".MEMF_LOCAL")==0) {
|
|
memflags |= MEMF_LOCAL;
|
|
} else if (strcmp(nameext, ".MEMF_KICK")==0) {
|
|
memflags |= MEMF_KICK;
|
|
} else if (strcmp(nameext, ".MEMF_FAST")==0) {
|
|
memflags |= MEMF_FAST;
|
|
} else if (strcmp(nameext, ".MEMF_PUBLIC")==0) {
|
|
memflags |= MEMF_PUBLIC;
|
|
}
|
|
}
|
|
}
|
|
|
|
hunk = ilsAllocMem(hunk_size, memflags | MEMF_PUBLIC | (sh->type == SHT_NOBITS ? MEMF_CLEAR : 0));
|
|
if (hunk)
|
|
{
|
|
hunk->next = 0;
|
|
hunk->size = hunk_size;
|
|
|
|
/* In case we are required to honour alignment, and If this section contains
|
|
executable code, create a trampoline to its beginning, so that even if the
|
|
alignment requirements make the actual code go much after the end of the
|
|
hunk structure, the code can still be reached in the usual way. */
|
|
if (do_align)
|
|
{
|
|
if (sh->flags & SHF_EXECINSTR)
|
|
{
|
|
sh->addr = (char *)AROS_ROUNDUP2
|
|
(
|
|
(IPTR)hunk->data + sizeof(struct FullJumpVec), sh->addralign
|
|
);
|
|
__AROS_SET_FULLJMP((struct FullJumpVec *)hunk->data, sh->addr);
|
|
}
|
|
else
|
|
sh->addr = (char *)AROS_ROUNDUP2((IPTR)hunk->data, sh->addralign);
|
|
}
|
|
else
|
|
sh->addr = hunk->data;
|
|
|
|
/* Link the previous one with the new one */
|
|
BPTR2HUNK(*next_hunk_ptr)->next = HUNK2BPTR(hunk);
|
|
|
|
D(bug("[dos] hunk @ %p, size=%08x, addr @ %p\n", hunk, hunk->size, sh->addr));
|
|
|
|
/* Update the pointer to the previous one, which is now the current one */
|
|
*next_hunk_ptr = (APTR)((IPTR)hunk + offsetof(struct hunk, next));
|
|
|
|
if (sh->type != SHT_NOBITS)
|
|
return !elf_read_block(file, sh->offset, sh->addr, sh->size, funcarray, srb, DOSBase);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
SetIoErr(ERROR_NO_FREE_STORE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int relocate
|
|
(
|
|
struct elfheader *eh,
|
|
struct sheader *sh,
|
|
ULONG shrel_idx,
|
|
struct sheader *symtab_shndx,
|
|
struct DosLibrary *DOSBase
|
|
)
|
|
{
|
|
struct sheader *shrel = &sh[shrel_idx];
|
|
struct sheader *shsymtab = &sh[shrel->link];
|
|
struct sheader *toreloc = &sh[shrel->info];
|
|
|
|
struct symbol *symtab = (struct symbol *)shsymtab->addr;
|
|
struct relo *rel = (struct relo *)shrel->addr;
|
|
|
|
/*
|
|
* Ignore relocs if the target section has no allocation. that can happen
|
|
* eg. with a .debug PROGBITS and a .rel.debug section
|
|
*/
|
|
if (!(toreloc->flags & SHF_ALLOC))
|
|
return 1;
|
|
|
|
ULONG numrel = shrel->size / shrel->entsize;
|
|
ULONG i;
|
|
|
|
for (i=0; i<numrel; i++, rel++)
|
|
{
|
|
struct symbol *sym;
|
|
ULONG *p;
|
|
IPTR s;
|
|
ULONG shindex;
|
|
|
|
#ifdef __arm__
|
|
/*
|
|
* R_ARM_V4BX are actually special marks for the linker.
|
|
* They even never have a target (shindex == SHN_UNDEF),
|
|
* so we simply ignore them before doing any checks.
|
|
*/
|
|
if (ELF_R_TYPE(rel->info) == R_ARM_V4BX)
|
|
continue;
|
|
#endif
|
|
|
|
sym = &symtab[ELF_R_SYM(rel->info)];
|
|
p = toreloc->addr + rel->offset;
|
|
|
|
if (sym->shindex != SHN_XINDEX)
|
|
shindex = sym->shindex;
|
|
|
|
else {
|
|
if (symtab_shndx == NULL) {
|
|
D(bug("[ELF Loader] got symbol with shndx 0xfff, but there's no symtab shndx table\n"));
|
|
SetIoErr(ERROR_BAD_HUNK);
|
|
return 0;
|
|
}
|
|
shindex = ((ULONG *)symtab_shndx->addr)[ELF_R_SYM(rel->info)];
|
|
}
|
|
|
|
DB2(bug("[ELF Loader] Processing symbol %s\n", sh[shsymtab->link].addr + sym->name));
|
|
|
|
switch (shindex)
|
|
{
|
|
|
|
case SHN_COMMON:
|
|
D(bug("[ELF Loader] COMMON symbol '%s'\n",
|
|
(STRPTR)sh[shsymtab->link].addr + sym->name));
|
|
SetIoErr(ERROR_BAD_HUNK);
|
|
|
|
return 0;
|
|
|
|
case SHN_ABS:
|
|
s = sym->value;
|
|
break;
|
|
|
|
case SHN_UNDEF:
|
|
if (ELF_R_TYPE(rel->info) != 0) {
|
|
D(bug("[ELF Loader] Undefined symbol '%s'\n",
|
|
(STRPTR)sh[shsymtab->link].addr + sym->name));
|
|
SetIoErr(ERROR_BAD_HUNK);
|
|
return 0;
|
|
}
|
|
/* fall through */
|
|
|
|
default:
|
|
s = (IPTR)sh[shindex].addr + sym->value;
|
|
}
|
|
|
|
switch (ELF_R_TYPE(rel->info))
|
|
{
|
|
#if defined(__i386__)
|
|
|
|
case R_386_32: /* 32bit absolute */
|
|
*p += s;
|
|
break;
|
|
|
|
case R_386_PC32: /* 32bit PC relative */
|
|
*p += s - (ULONG)p;
|
|
break;
|
|
|
|
case R_386_NONE:
|
|
break;
|
|
|
|
#elif defined(__x86_64__)
|
|
case R_X86_64_64: /* 64bit direct/absolute */
|
|
*(UQUAD *)p = s + rel->addend;
|
|
break;
|
|
|
|
case R_X86_64_PLT32:
|
|
case R_X86_64_PC32: /* PC relative 32 bit signed */
|
|
*(ULONG *)p = s + rel->addend - (IPTR) p;
|
|
break;
|
|
|
|
case R_X86_64_32:
|
|
*(ULONG *)p = (UQUAD)s + (UQUAD)rel->addend;
|
|
break;
|
|
|
|
case R_X86_64_32S:
|
|
*(LONG *)p = (QUAD)s + (QUAD)rel->addend;
|
|
break;
|
|
|
|
case R_X86_64_PC64:
|
|
*(UQUAD *)p = (UQUAD)s + (UQUAD)rel->addend - (IPTR) p;
|
|
break;
|
|
|
|
case R_X86_64_NONE: /* No reloc */
|
|
break;
|
|
|
|
#elif defined(__mc68000__)
|
|
|
|
case R_68K_32:
|
|
*p = s + rel->addend;
|
|
break;
|
|
|
|
case R_68K_16:
|
|
*(UWORD *)p = s + rel->addend;
|
|
break;
|
|
|
|
case R_68K_8:
|
|
*(UBYTE *)p = s + rel->addend;
|
|
break;
|
|
|
|
case R_68K_PC32:
|
|
*p = s + rel->addend - (ULONG)p;
|
|
break;
|
|
|
|
case R_68K_PC16:
|
|
*(UWORD *)p = s + rel->addend - (ULONG)p;
|
|
break;
|
|
|
|
case R_68K_PC8:
|
|
*(UBYTE *)p = s + rel->addend - (ULONG)p;
|
|
break;
|
|
|
|
case R_68K_NONE:
|
|
break;
|
|
|
|
#elif defined(__ppc__) || defined(__powerpc__)
|
|
|
|
case R_PPC_ADDR32:
|
|
*p = s + rel->addend;
|
|
break;
|
|
|
|
case R_PPC_ADDR16_LO:
|
|
{
|
|
unsigned short *c = (unsigned short *) p;
|
|
*c = (s + rel->addend) & 0xffff;
|
|
}
|
|
break;
|
|
|
|
case R_PPC_ADDR16_HA:
|
|
{
|
|
unsigned short *c = (unsigned short *) p;
|
|
ULONG temp = s + rel->addend;
|
|
*c = temp >> 16;
|
|
if ((temp & 0x8000) != 0)
|
|
(*c)++;
|
|
}
|
|
break;
|
|
|
|
case R_PPC_REL16_LO:
|
|
{
|
|
unsigned short *c = (unsigned short *) p;
|
|
*c = (s + rel->addend - (ULONG) p) & 0xffff;
|
|
}
|
|
break;
|
|
|
|
case R_PPC_REL16_HA:
|
|
{
|
|
unsigned short *c = (unsigned short *) p;
|
|
ULONG temp = s + rel->addend - (ULONG) p;
|
|
*c = temp >> 16;
|
|
if ((temp & 0x8000) != 0)
|
|
(*c)++;
|
|
}
|
|
break;
|
|
|
|
case R_PPC_REL24:
|
|
*p &= ~0x3fffffc;
|
|
*p |= (s + rel->addend - (ULONG) p) & 0x3fffffc;
|
|
break;
|
|
|
|
case R_PPC_REL32:
|
|
*p = s + rel->addend - (ULONG) p;
|
|
break;
|
|
|
|
case R_PPC_NONE:
|
|
break;
|
|
|
|
#elif defined(__arm__)
|
|
case R_ARM_CALL:
|
|
case R_ARM_JUMP24:
|
|
case R_ARM_PC24:
|
|
case R_ARM_PREL31:
|
|
{
|
|
/* On ARM the 24 bit offset is shifted by 2 to the right */
|
|
signed long offset = (AROS_LE2LONG(*p) & 0x00ffffff) << 2;
|
|
/* If highest bit set, make offset negative */
|
|
if (offset & 0x02000000)
|
|
offset -= 0x04000000;
|
|
|
|
if (offset >= 0x02000000 ||
|
|
offset <= -0x02000000)
|
|
{
|
|
bug("[ELF Loader] Relocation type %d %d out of range!\n", i, ELF_R_TYPE(rel->info));
|
|
SetIoErr(ERROR_BAD_HUNK);
|
|
return 0;
|
|
}
|
|
offset += s - (ULONG)p;
|
|
|
|
offset >>= 2;
|
|
*p &= AROS_LONG2LE(0xff000000);
|
|
*p |= AROS_LONG2LE(offset & 0x00ffffff);
|
|
}
|
|
break;
|
|
|
|
case R_ARM_THM_CALL:
|
|
case R_ARM_THM_JUMP24:
|
|
{
|
|
ULONG upper,lower,sign,j1,j2;
|
|
LONG offset;
|
|
|
|
upper = AROS_WORD2LE(*((UWORD *)p));
|
|
lower = AROS_WORD2LE(*((UWORD *)p+1));
|
|
|
|
sign = (upper >> 10) & 1;
|
|
j1 = (lower >> 13) & 1;
|
|
j2 = (lower >> 11) & 1;
|
|
|
|
offset = (sign << 24) | ((~(j1 ^ sign) & 1) << 23) |
|
|
((~(j2 ^ sign) & 1) << 22) |
|
|
((upper & 0x03ff) << 12) |
|
|
((lower & 0x07ff) << 1);
|
|
|
|
if (offset & 0x01000000)
|
|
offset -= 0x02000000;
|
|
|
|
if (offset >= 0x01000000 ||
|
|
offset <= -0x01000000)
|
|
{
|
|
bug("[ELF Loader] Relocation type %d %d out of range!\n", i, ELF_R_TYPE(rel->info));
|
|
SetIoErr(ERROR_BAD_HUNK);
|
|
return 0;
|
|
}
|
|
offset += s - (ULONG)p;
|
|
|
|
sign = (offset >> 24) & 1;
|
|
j1 = sign ^ (~(offset >> 23) & 1);
|
|
j2 = sign ^ (~(offset >> 22) & 1);
|
|
|
|
*(UWORD *)p = AROS_WORD2LE((UWORD)((upper & 0xf800) | (sign << 10) |
|
|
((offset >> 12) & 0x03ff)));
|
|
*((UWORD *)p + 1) = AROS_WORD2LE((UWORD)((lower & 0xd000) |
|
|
(j1 << 13) | (j2 << 11) | ((offset >> 1) & 0x07ff)));
|
|
|
|
}
|
|
break;
|
|
|
|
case R_ARM_THM_MOVW_ABS_NC:
|
|
case R_ARM_THM_MOVT_ABS:
|
|
{
|
|
ULONG upper,lower;
|
|
LONG offset;
|
|
|
|
upper = AROS_LE2WORD(*((UWORD *)p));
|
|
lower = AROS_LE2WORD(*((UWORD *)p+1));
|
|
|
|
offset = ((upper & 0x000f) << 12) |
|
|
((upper & 0x0400) << 1) |
|
|
((lower & 0x7000) >> 4) |
|
|
(lower & 0x00ff);
|
|
|
|
offset = (offset ^ 0x8000) - 0x8000;
|
|
|
|
offset += s;
|
|
|
|
if (ELF_R_TYPE(rel->info) == R_ARM_THM_MOVT_ABS)
|
|
offset >>= 16;
|
|
|
|
*(UWORD *)p = AROS_WORD2LE((UWORD)((upper & 0xfbf0) |
|
|
((offset & 0xf000) >> 12) |
|
|
((offset & 0x0800) >> 1)));
|
|
*((UWORD *)p + 1) = AROS_WORD2LE((UWORD)((lower & 0x8f00) |
|
|
((offset & 0x0700)<< 4) |
|
|
(offset & 0x00ff)));
|
|
}
|
|
break;
|
|
|
|
case R_ARM_MOVW_ABS_NC:
|
|
case R_ARM_MOVT_ABS:
|
|
{
|
|
signed long offset = AROS_LE2LONG(*p);
|
|
offset = ((offset & 0xf0000) >> 4) | (offset & 0xfff);
|
|
offset = (offset ^ 0x8000) - 0x8000;
|
|
|
|
offset += s;
|
|
|
|
if (ELF_R_TYPE(rel->info) == R_ARM_MOVT_ABS)
|
|
offset >>= 16;
|
|
|
|
*p &= AROS_LONG2LE(0xfff0f000);
|
|
*p |= AROS_LONG2LE(((offset & 0xf000) << 4) | (offset & 0x0fff));
|
|
}
|
|
break;
|
|
|
|
case R_ARM_TARGET2: /* maps to R_ARM_ABS32 under EABI for AROS*/
|
|
case R_ARM_TARGET1: /* use for constructors/destructors; maps to
|
|
R_ARM_ABS32 */
|
|
case R_ARM_ABS32:
|
|
*p += s;
|
|
break;
|
|
|
|
case R_ARM_NONE:
|
|
break;
|
|
|
|
#else
|
|
# error Your architecture is not supported
|
|
#endif
|
|
|
|
default:
|
|
bug("[ELF Loader] Unknown relocation #%d type %d\n", i, ELF_R_TYPE(rel->info));
|
|
SetIoErr(ERROR_BAD_HUNK);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
#ifdef __arm__
|
|
|
|
/*
|
|
* On ARM < v6 all LONG accesses must be LONG-aligned
|
|
* TODO: This is useful and can be moved to some public include file.
|
|
*/
|
|
#if (__ARM_ARCH__ > 5)
|
|
|
|
#define READLONG_UNALIGNED(src) src
|
|
|
|
#else
|
|
|
|
static inline ULONG readlong_unaligned(ULONG *src)
|
|
{
|
|
ULONG res, tmp;
|
|
|
|
asm volatile(
|
|
"ldrb %0, [%2, #0]\n\t"
|
|
"ldrb %1, [%2, #1]\n\t"
|
|
"orr %0, %0, %1, lsl #8\n\t"
|
|
"ldrb %1, [%2, #2]\n\t"
|
|
"orr %0, %0, %1, lsl #16\n\t"
|
|
"ldrb %1, [%2, #3]\n\t"
|
|
"orr %0, %0, %1, lsl #24"
|
|
:"=&r"(res), "=&r"(tmp) : "r"(src)
|
|
);
|
|
|
|
return res;
|
|
}
|
|
|
|
#define READLONG_UNALIGNED(src) readlong_unaligned(&src);
|
|
#endif
|
|
|
|
/*
|
|
* This code parses special .ARM.Attributes section and
|
|
* extracts system requirements from it. Things like float ABI,
|
|
* minimum CPU and FPU version are described there.
|
|
*/
|
|
|
|
static UBYTE arm_cpus[] =
|
|
{
|
|
CPUFAMILY_ARM_3,
|
|
CPUFAMILY_ARM_4,
|
|
CPUFAMILY_ARM_4T,
|
|
CPUFAMILY_ARM_5T,
|
|
CPUFAMILY_ARM_5TE,
|
|
CPUFAMILY_ARM_5TEJ,
|
|
CPUFAMILY_ARM_6,
|
|
CPUFAMILY_ARM_6,
|
|
CPUFAMILY_ARM_6, /* 6KZ */
|
|
CPUFAMILY_ARM_6, /* 6T2 */
|
|
CPUFAMILY_ARM_6, /* 6K */
|
|
CPUFAMILY_ARM_7,
|
|
CPUFAMILY_ARM_6, /* 6-M */
|
|
CPUFAMILY_ARM_6, /* 6S-M */
|
|
CPUFAMILY_ARM_7 /* 7E-M */
|
|
};
|
|
|
|
static BOOL ARM_ParseAttrs(UBYTE *data, ULONG len, struct DosLibrary *DOSBase)
|
|
{
|
|
struct attrs_section *attrs;
|
|
|
|
if (data[0] != ATTR_VERSION_CURRENT)
|
|
{
|
|
DATTR(bug("[ELF.ARM] Unknown attributes version: 0x%02\n", data[0]));
|
|
return FALSE;
|
|
}
|
|
|
|
attrs = (void *)data + 1;
|
|
while (len > 0)
|
|
{
|
|
ULONG attrs_size = READLONG_UNALIGNED(attrs->size);
|
|
|
|
if (!strcmp(attrs->vendor, "aeabi"))
|
|
{
|
|
struct attrs_subsection *aeabi_attrs = (void *)attrs->vendor + 6;
|
|
ULONG aeabi_len = attrs_size - 10;
|
|
|
|
DATTR(bug("[ELF.ARM] Found aeabi attributes @ 0x%p (length %u)\n", aeabi_attrs, aeabi_len));
|
|
|
|
while (aeabi_len > 0)
|
|
{
|
|
ULONG aeabi_attrs_size = READLONG_UNALIGNED(aeabi_attrs->size);
|
|
|
|
if (aeabi_attrs->tag == Tag_File)
|
|
{
|
|
UBYTE *file_subsection = (void *)aeabi_attrs + sizeof(struct attrs_subsection);
|
|
UBYTE file_len = aeabi_attrs_size - sizeof(struct attrs_subsection);
|
|
|
|
DATTR(bug("[ELF.ARM] Found file-wide attributes @ 0x%p (length %u)\n", file_subsection, file_len));
|
|
|
|
while (file_len > 0)
|
|
{
|
|
UBYTE tag, shift;
|
|
ULONG val = 0;
|
|
|
|
tag = *file_subsection++;
|
|
file_len--;
|
|
|
|
if (file_len == 0)
|
|
{
|
|
DATTR(bug("[ELF.ARM] Mailformed attribute tag %d (no data)\n", tag));
|
|
return FALSE;
|
|
}
|
|
|
|
switch (tag)
|
|
{
|
|
case Tag_CPU_raw_name:
|
|
case Tag_CPU_name:
|
|
case Tag_compatibility:
|
|
case Tag_also_compatible_with:
|
|
case Tag_conformance:
|
|
/* These two are NULL-terminated strings. Just skip. */
|
|
while (file_len)
|
|
{
|
|
file_len--;
|
|
if (*file_subsection++ == 0)
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
/* Read ULEB128 value */
|
|
shift = 0;
|
|
while (file_len)
|
|
{
|
|
UBYTE byte;
|
|
|
|
byte = *file_subsection++;
|
|
file_len--;
|
|
|
|
val |= (byte & 0x7F) << shift;
|
|
if (!(byte & 0x80))
|
|
break;
|
|
|
|
shift += 7;
|
|
}
|
|
}
|
|
|
|
switch (tag)
|
|
{
|
|
case Tag_CPU_arch:
|
|
DATTR(bug("[ELF.ARM] ARM CPU architecture set to %d\n", val));
|
|
|
|
if (val > ELF_CPU_ARMv7EM)
|
|
{
|
|
DATTR(bug("[ELF.ARM] Unknown CPU tag value (%d)\n", val));
|
|
return FALSE;
|
|
}
|
|
|
|
if (arm_cpus[val] > IDosBase(DOSBase)->arm_Arch)
|
|
{
|
|
DATTR(bug("[ELF.ARM] CPU Requirements too high (system %d, file %d)\n", IDosBase(DOSBase)->arm_Arch, arm_cpus[val]));
|
|
return FALSE;
|
|
}
|
|
break;
|
|
|
|
case Tag_FP_arch:
|
|
DATTR(bug("[ELF.ARM] ARM FPU architecture set to %d\n", val));
|
|
|
|
switch (val)
|
|
{
|
|
case ELF_FP_None:
|
|
break;
|
|
|
|
case ELF_FP_v1:
|
|
case ELF_FP_v2:
|
|
if (!IDosBase(DOSBase)->arm_VFP)
|
|
{
|
|
DATTR(bug("[ELF.ARM] VFP required but missing\n"));
|
|
return FALSE;
|
|
}
|
|
break;
|
|
|
|
case ELF_FP_v3:
|
|
case ELF_FP_v3_Short:
|
|
if (!IDosBase(DOSBase)->arm_VFP_v3)
|
|
{
|
|
DATTR(bug("[ELF.ARM] VFPv3 required but missing\n"));
|
|
return FALSE;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
/* This includes VFPv4 for now */
|
|
DATTR(bug("[ELF.ARM] VFP %d required -- unsupported\n", val));
|
|
return FALSE;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* We allow to execute only files which contain attributes section */
|
|
return TRUE;
|
|
}
|
|
aeabi_attrs = (void *)aeabi_attrs + aeabi_attrs_size;
|
|
aeabi_len -= aeabi_attrs_size;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
attrs = (void *)attrs + attrs_size;
|
|
len -= attrs_size;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
#endif
|
|
|
|
BPTR InternalLoadSeg_ELF
|
|
(
|
|
BPTR file,
|
|
BPTR table __unused,
|
|
SIPTR *funcarray,
|
|
LONG *stack __unused,
|
|
struct DosLibrary *DOSBase
|
|
)
|
|
{
|
|
struct elfheader eh;
|
|
struct sheader *sh;
|
|
struct sheader *symtab_shndx = NULL;
|
|
struct sheader *strtab = NULL;
|
|
BPTR hunks = 0;
|
|
#if defined(DOCACHECLEAR)
|
|
BPTR curr;
|
|
#endif
|
|
BPTR *next_hunk_ptr = &hunks;
|
|
ULONG i;
|
|
BOOL exec_hunk_seen = FALSE;
|
|
ULONG int_shnum;
|
|
struct SRBuffer srb = { 0 };
|
|
|
|
/* load and validate ELF header */
|
|
if (!load_header(file, &eh, funcarray, &srb, DOSBase))
|
|
return 0;
|
|
|
|
int_shnum = read_shnum(file, &eh, funcarray, &srb, DOSBase);
|
|
if (!int_shnum)
|
|
return 0;
|
|
|
|
/* load section headers */
|
|
if (!(sh = load_block(file, eh.shoff, int_shnum * eh.shentsize, funcarray, &srb, DOSBase)))
|
|
return 0;
|
|
|
|
#ifdef __arm__
|
|
for (i = 0; i < int_shnum; i++)
|
|
{
|
|
if (sh[i].type == SHT_ARM_ATTRIBUTES)
|
|
{
|
|
ULONG len = sh[i].size;
|
|
UBYTE *data = load_block(file, sh[i].offset, len, funcarray, &srb, DOSBase);
|
|
|
|
if (data)
|
|
{
|
|
BOOL res = ARM_ParseAttrs(data, len, DOSBase);
|
|
|
|
ilsFreeMem(data, len);
|
|
|
|
if (!res)
|
|
{
|
|
D(bug("[ELF Loader] Can't parse ARM attributes\n"));
|
|
SetIoErr(ERROR_NOT_EXECUTABLE);
|
|
goto error;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* Iterate over the section headers in order to do some stuff... */
|
|
for (i = 0; i < int_shnum; i++)
|
|
{
|
|
/*
|
|
Load the symbol and string table(s).
|
|
|
|
NOTICE: the ELF standard, at the moment (Nov 2002) explicitely states
|
|
that only one symbol table per file is allowed. However, it
|
|
also states that this may change in future... we already handle it.
|
|
*/
|
|
if (sh[i].type == SHT_SYMTAB || sh[i].type == SHT_STRTAB || sh[i].type == SHT_SYMTAB_SHNDX)
|
|
{
|
|
sh[i].addr = load_block(file, sh[i].offset, sh[i].size, funcarray, &srb, DOSBase);
|
|
if (!sh[i].addr)
|
|
goto error;
|
|
|
|
if (sh[i].type == SHT_STRTAB && i == eh.shstrndx) {
|
|
if (strtab == NULL) {
|
|
strtab = &sh[i];
|
|
} else {
|
|
D(bug("[ELF Loader] file contains multiple strtab tables. only using the first one\n"));
|
|
}
|
|
}
|
|
|
|
if (sh[i].type == SHT_SYMTAB_SHNDX) {
|
|
if (symtab_shndx == NULL)
|
|
symtab_shndx = &sh[i];
|
|
else
|
|
D(bug("[ELF Loader] file contains multiple symtab shndx tables. only using the first one\n"));
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Now that we have the string and symbol tables loaded,
|
|
* load the rest of the hunks.
|
|
*/
|
|
for (i = 0; i < int_shnum; i++)
|
|
{
|
|
/* Skip the already loaded hunks */
|
|
if (sh[i].type == SHT_SYMTAB || sh[i].type == SHT_STRTAB || sh[i].type == SHT_SYMTAB_SHNDX)
|
|
continue;
|
|
|
|
/* Load the section in memory if needed, and make a hunk out of it */
|
|
if (sh[i].flags & SHF_ALLOC)
|
|
{
|
|
if (sh[i].size)
|
|
{
|
|
/* Only allow alignment if this is an executable hunk
|
|
or if an executable hunk has been loaded already,
|
|
so to avoid the situation in which a data hunk has its
|
|
content displaced from the hunk's header in case it's the
|
|
first hunk (this happens with Keymaps, for instance). */
|
|
if (sh[i].flags & SHF_EXECINSTR)
|
|
exec_hunk_seen = TRUE;
|
|
|
|
if (!load_hunk(file, &next_hunk_ptr, &sh[i], strtab ? strtab->addr : NULL, funcarray, exec_hunk_seen, &srb, DOSBase))
|
|
goto error;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Relocate the sections */
|
|
for (i = 0; i < int_shnum; i++)
|
|
{
|
|
/* Does this relocation section refer to a hunk? If so, addr must be != 0 */
|
|
if ((sh[i].type == AROS_ELF_REL) && sh[sh[i].info].addr)
|
|
{
|
|
sh[i].addr = load_block(file, sh[i].offset, sh[i].size, funcarray, &srb, DOSBase);
|
|
if (!sh[i].addr || !relocate(&eh, sh, i, symtab_shndx, DOSBase))
|
|
goto error;
|
|
|
|
ilsFreeMem(sh[i].addr, sh[i].size);
|
|
sh[i].addr = NULL;
|
|
}
|
|
}
|
|
|
|
register_elf(file, hunks, &eh, sh, DOSBase);
|
|
goto end;
|
|
|
|
error:
|
|
|
|
/* There were some errors, deallocate The hunks */
|
|
|
|
InternalUnLoadSeg(hunks, (VOID_FUNC)funcarray[2]);
|
|
hunks = 0;
|
|
|
|
end:
|
|
|
|
#if defined(DOCACHECLEAR)
|
|
/* Clear the caches to let the CPU see the new data and instructions. */
|
|
curr = hunks;
|
|
|
|
while (curr)
|
|
{
|
|
struct hunk *hunk = BPTR2HUNK(BADDR(curr));
|
|
|
|
ils_ClearCache(hunk->data, hunk->size, CACRF_ClearD | CACRF_ClearI);
|
|
curr = hunk->next;
|
|
}
|
|
#endif
|
|
|
|
/* deallocate the symbol tables */
|
|
for (i = 0; i < int_shnum; i++)
|
|
{
|
|
if (((sh[i].type == SHT_SYMTAB) || (sh[i].type == SHT_STRTAB)) && (sh[i].addr != NULL))
|
|
ilsFreeMem(sh[i].addr, sh[i].size);
|
|
}
|
|
|
|
/* Free the section headers */
|
|
ilsFreeMem(sh, int_shnum * eh.shentsize);
|
|
|
|
if (srb.srb_Buffer) FreeMem(srb.srb_Buffer, LOADSEG_SMALL_READ);
|
|
|
|
return hunks;
|
|
}
|