summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--mk/config.mk.in2
-rw-r--r--rts/Linker.c345
-rw-r--r--rts/LinkerInternals.h6
3 files changed, 341 insertions, 12 deletions
diff --git a/mk/config.mk.in b/mk/config.mk.in
index 2482da869d..da13af1d93 100644
--- a/mk/config.mk.in
+++ b/mk/config.mk.in
@@ -172,7 +172,7 @@ GhcWithSMP=$(strip $(if $(filter YESNO, $(ArchSupportsSMP)$(GhcUnregisterised)),
# has support for this OS/ARCH combination.
OsSupportsGHCi=$(strip $(patsubst $(TargetOS_CPP), YES, $(findstring $(TargetOS_CPP), mingw32 cygwin32 linux solaris2 freebsd dragonfly netbsd openbsd darwin kfreebsdgnu)))
-ArchSupportsGHCi=$(strip $(patsubst $(TargetArch_CPP), YES, $(findstring $(TargetArch_CPP), i386 x86_64 powerpc sparc sparc64)))
+ArchSupportsGHCi=$(strip $(patsubst $(TargetArch_CPP), YES, $(findstring $(TargetArch_CPP), i386 x86_64 powerpc sparc sparc64 arm)))
ifeq "$(OsSupportsGHCi)$(ArchSupportsGHCi)" "YESYES"
GhcWithInterpreter=YES
diff --git a/rts/Linker.c b/rts/Linker.c
index 6fd36d8bd8..887cacc840 100644
--- a/rts/Linker.c
+++ b/rts/Linker.c
@@ -178,7 +178,7 @@ static pathchar* pathdup(pathchar *path)
static int ocVerifyImage_ELF ( ObjectCode* oc );
static int ocGetNames_ELF ( ObjectCode* oc );
static int ocResolve_ELF ( ObjectCode* oc );
-#if defined(powerpc_HOST_ARCH) || defined(x86_64_HOST_ARCH)
+#if defined(powerpc_HOST_ARCH) || defined(x86_64_HOST_ARCH) || defined(arm_HOST_ARCH)
static int ocAllocateSymbolExtras_ELF ( ObjectCode* oc );
#endif
#elif defined(OBJFORMAT_PEi386)
@@ -2266,7 +2266,7 @@ loadOc( ObjectCode* oc ) {
IF_DEBUG(linker, debugBelch("loadOc: ocAllocateSymbolExtras_MachO failed\n"));
return r;
}
-# elif defined(OBJFORMAT_ELF) && (defined(powerpc_HOST_ARCH) || defined(x86_64_HOST_ARCH))
+# elif defined(OBJFORMAT_ELF) && (defined(powerpc_HOST_ARCH) || defined(x86_64_HOST_ARCH) || defined(arm_HOST_ARCH))
r = ocAllocateSymbolExtras_ELF ( oc );
if (!r) {
IF_DEBUG(linker, debugBelch("loadOc: ocAllocateSymbolExtras_ELF failed\n"));
@@ -2471,13 +2471,13 @@ addSection ( ObjectCode* oc, SectionKind kind,
* them right next to the object code itself.
*/
-#if defined(powerpc_HOST_ARCH) || defined(x86_64_HOST_ARCH)
+#if defined(powerpc_HOST_ARCH) || defined(x86_64_HOST_ARCH) || defined(arm_HOST_ARCH)
/*
ocAllocateSymbolExtras
Allocate additional space at the end of the object file image to make room
- for jump islands (powerpc, x86_64) and GOT entries (x86_64).
+ for jump islands (powerpc, x86_64, arm) and GOT entries (x86_64).
PowerPC relative branch instructions have a 24 bit displacement field.
As PPC code is always 4-byte-aligned, this yields a +-32MB range.
@@ -2550,6 +2550,23 @@ static int ocAllocateSymbolExtras( ObjectCode* oc, int count, int first )
return 1;
}
+#endif // defined(powerpc_HOST_ARCH) || defined(x86_64_HOST_ARCH) || defined(arm_HOST_ARCH)
+
+#if defined(arm_HOST_ARCH)
+
+static void
+ocFlushInstructionCache( ObjectCode *oc )
+{
+ // Object code
+ __clear_cache(oc->image, oc->image + oc->fileSize);
+ // Jump islands
+ __clear_cache(oc->symbol_extras, &oc->symbol_extras[oc->n_symbol_extras]);
+}
+
+#endif
+
+#if defined(powerpc_HOST_ARCH) || defined(x86_64_HOST_ARCH)
+
static SymbolExtra* makeSymbolExtra( ObjectCode* oc,
unsigned long symbolNumber,
unsigned long target )
@@ -2577,7 +2594,7 @@ static SymbolExtra* makeSymbolExtra( ObjectCode* oc,
extra->jumpIsland.bctr = 0x4e800420;
#endif
#ifdef x86_64_HOST_ARCH
- // jmp *-14(%rip)
+ // jmp *-14(%rip)
static uint8_t jmp[] = { 0xFF, 0x25, 0xF2, 0xFF, 0xFF, 0xFF };
extra->addr = target;
memcpy(extra->jumpIsland, jmp, 6);
@@ -2586,7 +2603,72 @@ static SymbolExtra* makeSymbolExtra( ObjectCode* oc,
return extra;
}
-#endif
+#endif // defined(powerpc_HOST_ARCH) || defined(x86_64_HOST_ARCH)
+
+#ifdef arm_HOST_ARCH
+static SymbolExtra* makeArmSymbolExtra( ObjectCode* oc,
+ unsigned long symbolNumber,
+ unsigned long target,
+ int fromThumb,
+ int toThumb )
+{
+ SymbolExtra *extra;
+
+ ASSERT( symbolNumber >= oc->first_symbol_extra
+ && symbolNumber - oc->first_symbol_extra < oc->n_symbol_extras);
+
+ extra = &oc->symbol_extras[symbolNumber - oc->first_symbol_extra];
+
+ // Make sure instruction mode bit is set properly
+ if (toThumb)
+ target |= 1;
+ else
+ target &= ~1;
+
+ if (!fromThumb) {
+ // In ARM encoding:
+ // movw r12, #0
+ // movt r12, #0
+ // bx r12
+ uint32_t code[] = { 0xe300c000, 0xe340c000, 0xe12fff1c };
+
+ // Patch lower half-word into movw
+ code[0] |= ((target>>12) & 0xf) << 16;
+ code[0] |= target & 0xfff;
+ // Patch upper half-word into movt
+ target >>= 16;
+ code[1] |= ((target>>12) & 0xf) << 16;
+ code[1] |= target & 0xfff;
+
+ memcpy(extra->jumpIsland, code, 12);
+
+ } else {
+ // In Thumb encoding:
+ // movw r12, #0
+ // movt r12, #0
+ // bx r12
+ uint16_t code[] = { 0xf240, 0x0c00,
+ 0xf2c0, 0x0c00,
+ 0x4760 };
+
+ // Patch lower half-word into movw
+ code[0] |= (target>>12) & 0xf;
+ code[0] |= ((target>>11) & 0x1) << 10;
+ code[1] |= ((target>>8) & 0x7) << 12;
+ code[1] |= target & 0xff;
+ // Patch upper half-word into movt
+ target >>= 16;
+ code[2] |= (target>>12) & 0xf;
+ code[2] |= ((target>>11) & 0x1) << 10;
+ code[3] |= ((target>>8) & 0x7) << 12;
+ code[3] |= target & 0xff;
+
+ memcpy(extra->jumpIsland, code, 10);
+ }
+
+ return extra;
+}
+#endif // arm_HOST_ARCH
/* --------------------------------------------------------------------------
* PowerPC specifics (instruction cache flushing)
@@ -3582,6 +3664,44 @@ ocResolve_PEi386 ( ObjectCode* oc )
# define R_X86_64_PC64 24
# endif
+/*
+ * Workaround for libc implementations (e.g. eglibc) with incomplete
+ * relocation lists
+ */
+#ifndef R_ARM_THM_CALL
+# define R_ARM_THM_CALL 10
+#endif
+#ifndef R_ARM_CALL
+# define R_ARM_CALL 28
+#endif
+#ifndef R_ARM_JUMP24
+# define R_ARM_JUMP24 29
+#endif
+#ifndef R_ARM_THM_JUMP24
+# define R_ARM_THM_JUMP24 30
+#endif
+#ifndef R_ARM_TARGET1
+# define R_ARM_TARGET1 38
+#endif
+#ifndef R_ARM_MOVW_ABS_NC
+# define R_ARM_MOVW_ABS_NC 43
+#endif
+#ifndef R_ARM_MOVT_ABS
+# define R_ARM_MOVT_ABS 44
+#endif
+#ifndef R_ARM_THM_MOVW_ABS_NC
+# define R_ARM_THM_MOVW_ABS_NC 47
+#endif
+#ifndef R_ARM_THM_MOVT_ABS
+# define R_ARM_THM_MOVT_ABS 48
+#endif
+#ifndef R_ARM_THM_JUMP11
+# define R_ARM_THM_JUMP11 102
+#endif
+#ifndef R_ARM_THM_JUMP8
+# define R_ARM_THM_JUMP8 103
+#endif
+
/*
* Define a set of types which can be used for both ELF32 and ELF64
*/
@@ -3776,6 +3896,9 @@ ocVerifyImage_ELF ( ObjectCode* oc )
IF_DEBUG(linker,debugBelch( "Architecture is " ));
switch (ehdr->e_machine) {
+#ifdef EM_ARM
+ case EM_ARM: IF_DEBUG(linker,debugBelch( "arm" )); break;
+#endif
case EM_386: IF_DEBUG(linker,debugBelch( "x86" )); break;
#ifdef EM_SPARC32PLUS
case EM_SPARC32PLUS:
@@ -4137,7 +4260,7 @@ ocGetNames_ELF ( ObjectCode* oc )
}
/* Do ELF relocations which lack an explicit addend. All x86-linux
- relocations appear to be of this form. */
+ and arm-linux relocations appear to be of this form. */
static int
do_Elf_Rel_relocations ( ObjectCode* oc, char* ehdrC,
Elf_Shdr* shdr, int shnum )
@@ -4185,6 +4308,9 @@ do_Elf_Rel_relocations ( ObjectCode* oc, char* ehdrC,
#endif
StgStablePtr stablePtr;
StgPtr stableVal;
+#ifdef arm_HOST_ARCH
+ int is_target_thm=0, T=0;
+#endif
IF_DEBUG(linker,debugBelch( "Rel entry %3d is raw(%6p %6p)",
j, (void*)offset, (void*)info ));
@@ -4220,6 +4346,17 @@ do_Elf_Rel_relocations ( ObjectCode* oc, char* ehdrC,
return 0;
}
IF_DEBUG(linker,debugBelch( "`%s' resolves to %p\n", symbol, (void*)S ));
+
+#ifdef arm_HOST_ARCH
+ // Thumb instructions have bit 0 of symbol's st_value set
+ is_target_thm = S & 0x1;
+ T = sym.st_info & STT_FUNC && is_target_thm;
+
+ // Make sure we clear bit 0. Strictly speaking we should have done
+ // this to st_value above but I believe alignment requirements should
+ // ensure that no instructions start on an odd address
+ S &= ~1;
+#endif
}
IF_DEBUG(linker,debugBelch( "Reloc: P = %p S = %p A = %p\n",
@@ -4235,6 +4372,196 @@ do_Elf_Rel_relocations ( ObjectCode* oc, char* ehdrC,
case R_386_32: *pP = value; break;
case R_386_PC32: *pP = value - P; break;
# endif
+
+# ifdef arm_HOST_ARCH
+ case R_ARM_ABS32:
+ case R_ARM_TARGET1: // Specified by Linux ARM ABI to be equivalent to ABS32
+ *(Elf32_Word *)P += S;
+ *(Elf32_Word *)P |= T;
+ break;
+
+ case R_ARM_REL32:
+ *(Elf32_Word *)P += S;
+ *(Elf32_Word *)P |= T;
+ *(Elf32_Word *)P -= P;
+ break;
+
+ case R_ARM_CALL:
+ case R_ARM_JUMP24:
+ {
+ StgWord32 *word = (StgWord32 *)P;
+ StgInt32 imm = (*word & 0x00ffffff) << 2;
+ StgInt32 offset;
+ int overflow;
+
+ // Sign extend 24 to 32 bits
+ if (imm & 0x02000000)
+ imm -= 0x04000000;
+ offset = ((S + imm) | T) - P;
+
+ overflow = offset <= (StgInt32)0xfe000000 || offset >= (StgInt32)0x02000000;
+
+ if ((is_target_thm && ELF_R_TYPE(info) == R_ARM_JUMP24) || overflow) {
+ // Generate veneer
+ // The +8 below is to undo the PC-bias compensation done by the object producer
+ SymbolExtra *extra = makeArmSymbolExtra(oc, ELF_R_SYM(info), S+imm+8, 0, is_target_thm);
+ // The -8 below is to compensate for PC bias
+ offset = (StgWord32) &extra->jumpIsland - P - 8;
+ offset &= ~1; // Clear thumb indicator bit
+ } else if (is_target_thm && ELF_R_TYPE(info) == R_ARM_CALL) {
+ StgWord32 cond = (*word & 0xf0000000) >> 28;
+ if (cond == 0xe) {
+ // Change instruction to BLX
+ *word |= 0xf0000000; // Set first nibble
+ *word = (*word & ~0x01ffffff)
+ | ((offset >> 2) & 0x00ffffff) // imm24
+ | ((offset & 0x2) << 23); // H
+ break;
+ } else {
+ errorBelch("%s: Can't transition from ARM to Thumb when cond != 0xe\n",
+ oc->fileName);
+ return 0;
+ }
+ }
+
+ offset >>= 2;
+ *word = (*word & ~0x00ffffff)
+ | (offset & 0x00ffffff);
+ break;
+ }
+
+ case R_ARM_MOVT_ABS:
+ case R_ARM_MOVW_ABS_NC:
+ {
+ StgWord32 *word = (StgWord32 *)P;
+ StgInt32 offset = ((*word & 0xf0000) >> 4)
+ | (*word & 0xfff);
+ // Sign extend from 16 to 32 bits
+ offset = (offset ^ 0x8000) - 0x8000;
+
+ offset += S;
+ if (ELF_R_TYPE(info) == R_ARM_MOVT_ABS)
+ offset >>= 16;
+ else
+ offset |= T;
+
+ *word = (*word & 0xfff0f000)
+ | ((offset & 0xf000) << 4)
+ | (offset & 0x0fff);
+ break;
+ }
+
+ case R_ARM_THM_CALL:
+ case R_ARM_THM_JUMP24:
+ {
+ StgWord16 *upper = (StgWord16 *)P;
+ StgWord16 *lower = (StgWord16 *)(P + 2);
+
+ int overflow;
+ int to_thm = (*lower >> 12) & 1;
+ int sign = (*upper >> 10) & 1;
+ int j1, j2, i1, i2;
+
+ // Decode immediate value
+ j1 = (*lower >> 13) & 1; i1 = ~(j1 ^ sign) & 1;
+ j2 = (*lower >> 11) & 1; i2 = ~(j2 ^ sign) & 1;
+ StgInt32 imm = (sign << 24)
+ | (i1 << 23)
+ | (i2 << 22)
+ | ((*upper & 0x03ff) << 12)
+ | ((*lower & 0x07ff) << 1);
+
+ // Sign extend 25 to 32 bits
+ if (imm & 0x01000000)
+ imm -= 0x02000000;
+
+ offset = ((imm + S) | T) - P;
+ overflow = offset <= (StgWord32)0xff000000 || offset >= (StgWord32)0x01000000;
+
+ if ((!is_target_thm && ELF_R_TYPE(info) == R_ARM_THM_JUMP24) || overflow) {
+ // Generate veneer
+ SymbolExtra *extra = makeArmSymbolExtra(oc, ELF_R_SYM(info), S+imm+4, 1, is_target_thm);
+ offset = (StgWord32) &extra->jumpIsland - P - 4;
+ to_thm = 1;
+ } else if (!is_target_thm && ELF_R_TYPE(info) == R_ARM_THM_CALL) {
+ offset &= ~0x3;
+ to_thm = 0;
+ }
+
+ // Reencode instruction
+ i1 = ~(offset >> 23) & 1; j1 = sign ^ i1;
+ i2 = ~(offset >> 22) & 1; j2 = sign ^ i2;
+ *upper = ( (*upper & 0xf800)
+ | (sign << 10)
+ | ((offset >> 12) & 0x03ff) );
+ *lower = ( (*lower & 0xd000)
+ | (j1 << 13)
+ | (to_thm << 12)
+ | (j2 << 11)
+ | ((offset >> 1) & 0x07ff) );
+ break;
+ }
+
+ case R_ARM_THM_MOVT_ABS:
+ case R_ARM_THM_MOVW_ABS_NC:
+ {
+ StgWord16 *upper = (StgWord16 *)P;
+ StgWord16 *lower = (StgWord16 *)(P + 2);
+ StgInt32 offset = ((*upper & 0x000f) << 12)
+ | ((*upper & 0x0400) << 1)
+ | ((*lower & 0x7000) >> 4)
+ | (*lower & 0x00ff);
+
+ offset = (offset ^ 0x8000) - 0x8000; // Sign extend
+ offset += S;
+ if (ELF_R_TYPE(info) == R_ARM_THM_MOVW_ABS_NC)
+ offset |= T;
+ else if (ELF_R_TYPE(info) == R_ARM_THM_MOVT_ABS)
+ offset >>= 16;
+
+ *upper = ( (*upper & 0xfbf0)
+ | ((offset & 0xf000) >> 12)
+ | ((offset & 0x0800) >> 1) );
+ *lower = ( (*lower & 0x8f00)
+ | ((offset & 0x0700) << 4)
+ | (offset & 0x00ff) );
+ break;
+ }
+
+ case R_ARM_THM_JUMP8:
+ {
+ StgWord16 *word = (StgWord16 *)P;
+ StgWord offset = *word & 0x01fe;
+ offset += S - P;
+ if (!is_target_thm) {
+ errorBelch("%s: Thumb to ARM transition with JUMP8 relocation not supported\n",
+ oc->fileName);
+ return 0;
+ }
+
+ *word = (*word & ~0x01fe)
+ | (offset & 0x01fe);
+ break;
+ }
+
+ case R_ARM_THM_JUMP11:
+ {
+ StgWord16 *word = (StgWord16 *)P;
+ StgWord offset = *word & 0x0ffe;
+ offset += S - P;
+ if (!is_target_thm) {
+ errorBelch("%s: Thumb to ARM transition with JUMP11 relocation not supported\n",
+ oc->fileName);
+ return 0;
+ }
+
+ *word = (*word & ~0x0ffe)
+ | (offset & 0x0ffe);
+ break;
+ }
+
+# endif // arm_HOST_ARCH
+
default:
errorBelch("%s: unhandled ELF relocation(Rel) type %lu\n",
oc->fileName, (lnat)ELF_R_TYPE(info));
@@ -4560,7 +4887,7 @@ ocResolve_ELF ( ObjectCode* oc )
}
}
-#if defined(powerpc_HOST_ARCH)
+#if defined(powerpc_HOST_ARCH) || defined(arm_HOST_ARCH)
ocFlushInstructionCache( oc );
#endif
@@ -4571,7 +4898,7 @@ ocResolve_ELF ( ObjectCode* oc )
* PowerPC & X86_64 ELF specifics
*/
-#if defined(powerpc_HOST_ARCH) || defined(x86_64_HOST_ARCH)
+#if defined(powerpc_HOST_ARCH) || defined(x86_64_HOST_ARCH) || defined(arm_HOST_ARCH)
static int ocAllocateSymbolExtras_ELF( ObjectCode *oc )
{
diff --git a/rts/LinkerInternals.h b/rts/LinkerInternals.h
index dd4d7ed939..864e0d1f2f 100644
--- a/rts/LinkerInternals.h
+++ b/rts/LinkerInternals.h
@@ -40,7 +40,7 @@ typedef
ProddableBlock;
/* Jump Islands are sniplets of machine code required for relative
- * address relocations on the PowerPC.
+ * address relocations on the PowerPC, x86_64 and ARM.
*/
typedef struct {
#ifdef powerpc_HOST_ARCH
@@ -53,6 +53,8 @@ typedef struct {
#elif x86_64_HOST_ARCH
uint64_t addr;
uint8_t jumpIsland[6];
+#elif arm_HOST_ARCH
+ uint8_t jumpIsland[16];
#endif
} SymbolExtra;
@@ -104,7 +106,7 @@ typedef struct _ObjectCode {
unsigned int pltIndex;
#endif
-#if powerpc_HOST_ARCH || x86_64_HOST_ARCH
+#if powerpc_HOST_ARCH || x86_64_HOST_ARCH || arm_HOST_ARCH
SymbolExtra *symbol_extras;
unsigned long first_symbol_extra;
unsigned long n_symbol_extras;