diff options
| author | Nick Kledzik <kledzik@apple.com> | 2014-07-28 23:06:09 +0000 |
|---|---|---|
| committer | Nick Kledzik <kledzik@apple.com> | 2014-07-28 23:06:09 +0000 |
| commit | 54fd4e5fcb5adad4fcb313ee94b4f2c9e4c1e69f (patch) | |
| tree | 798e8f6387a35c744c2ba0d28923477868f8b0d7 /lld/lib/ReaderWriter/MachO/ArchHandler_arm.cpp | |
| parent | bd1628a595b27f05c130c0b5127eee32abdf0e38 (diff) | |
| download | bcm5719-llvm-54fd4e5fcb5adad4fcb313ee94b4f2c9e4c1e69f.tar.gz bcm5719-llvm-54fd4e5fcb5adad4fcb313ee94b4f2c9e4c1e69f.zip | |
[mach-o] Implement interworking between thumb and arm code
All iOS arm processor support switching between arm and thumb mode at call sites
by using the BLX instruction (instead of BL). But the compiler does not know
the implementation mode for extern functions, so the linker must update BL/BLX
instructions to match what is linked is actually linked together. In addition,
pointers to functions (such as vtables) must have the low bit set if the target
of the pointer is a thumb mode function.
llvm-svn: 214140
Diffstat (limited to 'lld/lib/ReaderWriter/MachO/ArchHandler_arm.cpp')
| -rw-r--r-- | lld/lib/ReaderWriter/MachO/ArchHandler_arm.cpp | 160 |
1 files changed, 116 insertions, 44 deletions
diff --git a/lld/lib/ReaderWriter/MachO/ArchHandler_arm.cpp b/lld/lib/ReaderWriter/MachO/ArchHandler_arm.cpp index 9dfab0ea78c..4a775ab0c89 100644 --- a/lld/lib/ReaderWriter/MachO/ArchHandler_arm.cpp +++ b/lld/lib/ReaderWriter/MachO/ArchHandler_arm.cpp @@ -130,13 +130,15 @@ private: static bool isThumbMovt(uint32_t instruction); static bool isArmMovw(uint32_t instruction); static bool isArmMovt(uint32_t instruction); - static int32_t getDisplacementFromThumbBranch(uint32_t instruction); + static int32_t getDisplacementFromThumbBranch(uint32_t instruction, uint32_t); static int32_t getDisplacementFromArmBranch(uint32_t instruction); static uint16_t getWordFromThumbMov(uint32_t instruction); static uint16_t getWordFromArmMov(uint32_t instruction); static uint32_t clearThumbBit(uint32_t value, const Atom *target); - static uint32_t setDisplacementInArmBranch(uint32_t instr, int32_t disp); - static uint32_t setDisplacementInThumbBranch(uint32_t instr, int32_t disp); + static uint32_t setDisplacementInArmBranch(uint32_t instr, int32_t disp, + bool targetIsThumb); + static uint32_t setDisplacementInThumbBranch(uint32_t instr, uint32_t ia, + int32_t disp, bool targetThumb); static uint32_t setWordFromThumbMov(uint32_t instruction, uint16_t word); static uint32_t setWordFromArmMov(uint32_t instruction, uint16_t word); @@ -144,12 +146,14 @@ private: void applyFixupFinal(const Reference &ref, uint8_t *location, uint64_t fixupAddress, uint64_t targetAddress, - uint64_t inAtomAddress, bool &thumbMode); + uint64_t inAtomAddress, bool &thumbMode, + bool targetIsThumb); void applyFixupRelocatable(const Reference &ref, uint8_t *location, uint64_t fixupAddress, uint64_t targetAddress, - uint64_t inAtomAddress, bool &thumbMode); + uint64_t inAtomAddress, bool &thumbMode, + bool targetIsThumb); const bool _swap; }; @@ -256,23 +260,7 @@ bool ArchHandler_arm::isPairedReloc(const Relocation &reloc) { } } -int32_t ArchHandler_arm::getDisplacementFromThumbBranch(uint32_t instruction) { - uint32_t s = (instruction >> 10) & 0x1; - uint32_t j1 = (instruction >> 29) & 0x1; - uint32_t j2 = (instruction >> 27) & 0x1; - uint32_t imm10 = instruction & 0x3FF; - uint32_t imm11 = (instruction >> 16) & 0x7FF; - uint32_t i1 = (j1 == s); - uint32_t i2 = (j2 == s); - uint32_t dis = - (s << 24) | (i1 << 23) | (i2 << 22) | (imm10 << 12) | (imm11 << 1); - int32_t sdis = dis; - if (s) - return (sdis | 0xFE000000); - else - return sdis; -} - +/// Extract displacement from an ARM b/bl/blx instruction. int32_t ArchHandler_arm::getDisplacementFromArmBranch(uint32_t instruction) { // Sign-extend imm24 int32_t displacement = (instruction & 0x00FFFFFF) << 2; @@ -284,18 +272,88 @@ int32_t ArchHandler_arm::getDisplacementFromArmBranch(uint32_t instruction) { return displacement; } +/// Update an ARM b/bl/blx instruction, switching bl <-> blx as needed. uint32_t ArchHandler_arm::setDisplacementInArmBranch(uint32_t instruction, - int32_t displacement) { - // FIXME: handle BLX and out-of-range. + int32_t displacement, + bool targetIsThumb) { + assert((displacement <= 33554428) && (displacement > (-33554432)) + && "arm branch out of range"); + bool is_blx = ((instruction & 0xF0000000) == 0xF0000000); uint32_t newInstruction = (instruction & 0xFF000000); - newInstruction |= ((displacement >> 2) & 0x00FFFFFF); + uint32_t h = 0; + if (targetIsThumb) { + // Force use of BLX. + newInstruction = 0xFA000000; + if (!is_blx) { + bool isConditionalBranch = ((instruction & 0xF0000000) != 0xE0000000); + assert(!isConditionalBranch && "no conditional arm blx"); + bool is_bl = ((instruction & 0xFF000000) == 0xEB000000); + assert(is_bl && "no arm pc-rel BX instruction"); + } + if (displacement & 2) + h = 1; + } + else { + // Force use of B/BL. + if (is_blx) + newInstruction = 0xEB000000; + } + newInstruction |= (h << 24) | ((displacement >> 2) & 0x00FFFFFF); return newInstruction; } +/// Extract displacement from a thumb b/bl/blx instruction. +int32_t ArchHandler_arm::getDisplacementFromThumbBranch(uint32_t instruction, + uint32_t instrAddr) { + bool is_blx = ((instruction & 0xD000F800) == 0xC000F000); + uint32_t s = (instruction >> 10) & 0x1; + uint32_t j1 = (instruction >> 29) & 0x1; + uint32_t j2 = (instruction >> 27) & 0x1; + uint32_t imm10 = instruction & 0x3FF; + uint32_t imm11 = (instruction >> 16) & 0x7FF; + uint32_t i1 = (j1 == s); + uint32_t i2 = (j2 == s); + uint32_t dis = + (s << 24) | (i1 << 23) | (i2 << 22) | (imm10 << 12) | (imm11 << 1); + int32_t sdis = dis; + int32_t result = s ? (sdis | 0xFE000000) : sdis; + if (is_blx && (instrAddr & 0x2)) { + // The thumb blx instruction always has low bit of imm11 as zero. The way + // a 2-byte aligned blx can branch to a 4-byte aligned ARM target is that + // the blx instruction always 4-byte aligns the pc before adding the + // displacement from the blx. We must emulate that when decoding this. + result -= 2; + } + return result; +} + +/// Update a thumb b/bl/blx instruction, switching bl <-> blx as needed. uint32_t ArchHandler_arm::setDisplacementInThumbBranch(uint32_t instruction, - int32_t displacement) { - // FIXME: handle BLX and out-of-range. + uint32_t instrAddr, + int32_t displacement, + bool targetIsThumb) { + assert((displacement <= 16777214) && (displacement > (-16777216)) + && "thumb branch out of range"); + bool is_bl = ((instruction & 0xD000F800) == 0xD000F000); + bool is_blx = ((instruction & 0xD000F800) == 0xC000F000); + bool is_b = ((instruction & 0xD000F800) == 0x9000F000); uint32_t newInstruction = (instruction & 0xF800D000); + if (is_bl || is_blx) { + if (targetIsThumb) { + newInstruction = 0xD000F000; // Use bl + } else { + newInstruction = 0xC000F000; // Use blx + // See note in getDisplacementFromThumbBranch() about blx. + if (instrAddr & 0x2) + displacement += 2; + } + } else if (is_b) { + assert(!targetIsThumb && "no pc-rel thumb branch instruction that " + "switches to arm mode"); + } + else { + llvm_unreachable("thumb branch22 reloc on a non-branch instruction"); + } uint32_t s = (uint32_t)(displacement >> 24) & 0x1; uint32_t i1 = (uint32_t)(displacement >> 23) & 0x1; uint32_t i2 = (uint32_t)(displacement >> 22) & 0x1; @@ -392,19 +450,19 @@ std::error_code ArchHandler_arm::getReferenceInfo( if (E ec = atomFromSymbolIndex(reloc.symbol, target)) return ec; // Instruction contains branch to addend. - displacement = getDisplacementFromThumbBranch(instruction); + displacement = getDisplacementFromThumbBranch(instruction, fixupAddress); *addend = fixupAddress + 4 + displacement; return std::error_code(); case ARM_THUMB_RELOC_BR22 | rPcRel | rLength4: // ex: bl _foo (and _foo is defined) *kind = thumb_b22; - displacement = getDisplacementFromThumbBranch(instruction); + displacement = getDisplacementFromThumbBranch(instruction, fixupAddress); targetAddress = fixupAddress + 4 + displacement; return atomFromAddress(reloc.symbol, targetAddress, target, addend); case ARM_THUMB_RELOC_BR22 | rScattered | rPcRel | rLength4: // ex: bl _foo+4 (and _foo is defined) *kind = thumb_b22; - displacement = getDisplacementFromThumbBranch(instruction); + displacement = getDisplacementFromThumbBranch(instruction, fixupAddress); targetAddress = fixupAddress + 4 + displacement; if (E ec = atomFromAddress(0, reloc.value, target, addend)) return ec; @@ -745,13 +803,14 @@ void ArchHandler_arm::applyFixupFinal(const Reference &ref, uint8_t *location, uint64_t fixupAddress, uint64_t targetAddress, uint64_t inAtomAddress, - bool &thumbMode) { + bool &thumbMode, bool targetIsThumb) { if (ref.kindNamespace() != Reference::KindNamespace::mach_o) return; assert(ref.kindArch() == Reference::KindArch::ARM); int32_t *loc32 = reinterpret_cast<int32_t *>(location); int32_t displacement; uint16_t value16; + uint32_t value32; switch (ref.kindValue()) { case modeThumbCode: thumbMode = true; @@ -764,7 +823,9 @@ void ArchHandler_arm::applyFixupFinal(const Reference &ref, uint8_t *location, case thumb_b22: assert(thumbMode); displacement = (targetAddress - (fixupAddress + 4)) + ref.addend(); - write32(*loc32, _swap, setDisplacementInThumbBranch(*loc32, displacement)); + value32 = setDisplacementInThumbBranch(*loc32, fixupAddress, displacement, + targetIsThumb); + write32(*loc32, _swap, value32); break; case thumb_movw: assert(thumbMode); @@ -789,7 +850,8 @@ void ArchHandler_arm::applyFixupFinal(const Reference &ref, uint8_t *location, case arm_b24: assert(!thumbMode); displacement = (targetAddress - (fixupAddress + 8)) + ref.addend(); - *loc32 = setDisplacementInArmBranch(*loc32, displacement); + value32 = setDisplacementInArmBranch(*loc32, displacement, targetIsThumb); + write32(*loc32, _swap, value32); break; case arm_movw: assert(!thumbMode); @@ -812,7 +874,10 @@ void ArchHandler_arm::applyFixupFinal(const Reference &ref, uint8_t *location, write32(*loc32, _swap, setWordFromArmMov(*loc32, value16)); break; case pointer32: - write32(*loc32, _swap, targetAddress + ref.addend()); + if (targetIsThumb) + write32(*loc32, _swap, targetAddress + ref.addend() + 1); + else + write32(*loc32, _swap, targetAddress + ref.addend()); break; case delta32: write32(*loc32, _swap, targetAddress - fixupAddress + ref.addend()); @@ -839,18 +904,20 @@ void ArchHandler_arm::generateAtomContent(const DefinedAtom &atom, uint32_t offset = ref->offsetInAtom(); const Atom *target = ref->target(); uint64_t targetAddress = 0; - if (isa<DefinedAtom>(target)) + bool targetIsThumb = false; + if (const DefinedAtom *defTarg = dyn_cast<DefinedAtom>(target)) { targetAddress = findAddress(*target); + targetIsThumb = isThumbFunction(*defTarg); + } uint64_t atomAddress = findAddress(atom); uint64_t fixupAddress = atomAddress + offset; if (relocatable) { - applyFixupRelocatable(*ref, &atomContentBuffer[offset], - fixupAddress, targetAddress, - atomAddress, thumbMode); + applyFixupRelocatable(*ref, &atomContentBuffer[offset], fixupAddress, + targetAddress, atomAddress, thumbMode, + targetIsThumb); } else { - applyFixupFinal(*ref, &atomContentBuffer[offset], - fixupAddress, targetAddress, - atomAddress, thumbMode); + applyFixupFinal(*ref, &atomContentBuffer[offset], fixupAddress, + targetAddress, atomAddress, thumbMode, targetIsThumb); } } } @@ -882,11 +949,13 @@ void ArchHandler_arm::applyFixupRelocatable(const Reference &ref, uint64_t fixupAddress, uint64_t targetAddress, uint64_t inAtomAddress, - bool &thumbMode) { + bool &thumbMode, + bool targetIsThumb) { bool useExternalReloc = useExternalRelocationTo(*ref.target()); int32_t *loc32 = reinterpret_cast<int32_t *>(location); int32_t displacement; uint16_t value16; + uint32_t value32; switch (ref.kindValue()) { case modeThumbCode: thumbMode = true; @@ -902,7 +971,9 @@ void ArchHandler_arm::applyFixupRelocatable(const Reference &ref, displacement = (ref.addend() - (fixupAddress + 4)); else displacement = (targetAddress - (fixupAddress + 4)) + ref.addend(); - write32(*loc32, _swap, setDisplacementInThumbBranch(*loc32, displacement)); + value32 = setDisplacementInThumbBranch(*loc32, fixupAddress, displacement, + targetIsThumb); + write32(*loc32, _swap, value32); break; case thumb_movw: assert(thumbMode); @@ -936,7 +1007,8 @@ void ArchHandler_arm::applyFixupRelocatable(const Reference &ref, displacement = (ref.addend() - (fixupAddress + 8)); else displacement = (targetAddress - (fixupAddress + 8)) + ref.addend(); - write32(*loc32, _swap, setDisplacementInArmBranch(*loc32, displacement)); + value32 = setDisplacementInArmBranch(*loc32, displacement, targetIsThumb); + write32(*loc32, _swap, value32); break; case arm_movw: assert(!thumbMode); |

