6c9f681252
Previously we only supporting using the system pointer size (aka the `absptr` encoding) because `llvm-mc`'s CFI directives always generate EH frames with that encoding. But libffi uses 4-byte-encoded, hand-rolled EH frames, so this patch adds support for it. Fixes #56576. Reviewed By: #lld-macho, oontvoo Differential Revision: https://reviews.llvm.org/D130804
141 lines
4.4 KiB
C++
141 lines
4.4 KiB
C++
//===- EhFrame.cpp --------------------------------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "EhFrame.h"
|
|
#include "InputFiles.h"
|
|
|
|
#include "lld/Common/ErrorHandler.h"
|
|
#include "llvm/BinaryFormat/Dwarf.h"
|
|
#include "llvm/Support/Endian.h"
|
|
|
|
using namespace llvm;
|
|
using namespace lld;
|
|
using namespace lld::macho;
|
|
using namespace llvm::support::endian;
|
|
|
|
uint64_t EhReader::readLength(size_t *off) const {
|
|
const size_t errOff = *off;
|
|
if (*off + 4 > data.size())
|
|
failOn(errOff, "CIE/FDE too small");
|
|
uint64_t len = read32le(data.data() + *off);
|
|
*off += 4;
|
|
if (len == dwarf::DW_LENGTH_DWARF64) {
|
|
// FIXME: test this DWARF64 code path
|
|
if (*off + 8 > data.size())
|
|
failOn(errOff, "CIE/FDE too small");
|
|
len = read64le(data.data() + *off);
|
|
*off += 8;
|
|
}
|
|
if (*off + len > data.size())
|
|
failOn(errOff, "CIE/FDE extends past the end of the section");
|
|
return len;
|
|
}
|
|
|
|
void EhReader::skipValidLength(size_t *off) const {
|
|
uint32_t len = read32le(data.data() + *off);
|
|
*off += 4;
|
|
if (len == dwarf::DW_LENGTH_DWARF64)
|
|
*off += 8;
|
|
}
|
|
|
|
// Read a byte and advance off by one byte.
|
|
uint8_t EhReader::readByte(size_t *off) const {
|
|
if (*off + 1 > data.size())
|
|
failOn(*off, "unexpected end of CIE/FDE");
|
|
return data[(*off)++];
|
|
}
|
|
|
|
uint32_t EhReader::readU32(size_t *off) const {
|
|
if (*off + 4 > data.size())
|
|
failOn(*off, "unexpected end of CIE/FDE");
|
|
uint32_t v = read32le(data.data() + *off);
|
|
*off += 4;
|
|
return v;
|
|
}
|
|
|
|
uint64_t EhReader::readPointer(size_t *off, uint8_t size) const {
|
|
if (*off + size > data.size())
|
|
failOn(*off, "unexpected end of CIE/FDE");
|
|
uint64_t v;
|
|
if (size == 8)
|
|
v = read64le(data.data() + *off);
|
|
else {
|
|
assert(size == 4);
|
|
v = read32le(data.data() + *off);
|
|
}
|
|
*off += size;
|
|
return v;
|
|
}
|
|
|
|
// Read a null-terminated string.
|
|
StringRef EhReader::readString(size_t *off) const {
|
|
if (*off > data.size())
|
|
failOn(*off, "corrupted CIE (failed to read string)");
|
|
const size_t maxlen = data.size() - *off;
|
|
auto *c = reinterpret_cast<const char *>(data.data() + *off);
|
|
size_t len = strnlen(c, maxlen);
|
|
if (len == maxlen) // we failed to find the null terminator
|
|
failOn(*off, "corrupted CIE (failed to read string)");
|
|
*off += len + 1; // skip the null byte too
|
|
return StringRef(c, len);
|
|
}
|
|
|
|
void EhReader::skipLeb128(size_t *off) const {
|
|
const size_t errOff = *off;
|
|
while (*off < data.size()) {
|
|
uint8_t val = data[(*off)++];
|
|
if ((val & 0x80) == 0)
|
|
return;
|
|
}
|
|
failOn(errOff, "corrupted CIE (failed to read LEB128)");
|
|
}
|
|
|
|
void EhReader::failOn(size_t errOff, const Twine &msg) const {
|
|
fatal(toString(file) + ":(__eh_frame+0x" +
|
|
Twine::utohexstr(dataOff + errOff) + "): " + msg);
|
|
}
|
|
|
|
/*
|
|
* Create a pair of relocs to write the value of:
|
|
* `b - (offset + a)` if Invert == false
|
|
* `(a + offset) - b` if Invert == true
|
|
*/
|
|
template <bool Invert = false>
|
|
static void createSubtraction(PointerUnion<Symbol *, InputSection *> a,
|
|
PointerUnion<Symbol *, InputSection *> b,
|
|
uint64_t off, uint8_t length,
|
|
SmallVectorImpl<Reloc> *newRelocs) {
|
|
auto subtrahend = a;
|
|
auto minuend = b;
|
|
if (Invert)
|
|
std::swap(subtrahend, minuend);
|
|
assert(subtrahend.is<Symbol *>());
|
|
Reloc subtrahendReloc(target->subtractorRelocType, /*pcrel=*/false, length,
|
|
off, /*addend=*/0, subtrahend);
|
|
Reloc minuendReloc(target->unsignedRelocType, /*pcrel=*/false, length, off,
|
|
(Invert ? 1 : -1) * off, minuend);
|
|
newRelocs->push_back(subtrahendReloc);
|
|
newRelocs->push_back(minuendReloc);
|
|
}
|
|
|
|
void EhRelocator::makePcRel(uint64_t off,
|
|
PointerUnion<Symbol *, InputSection *> target,
|
|
uint8_t length) {
|
|
createSubtraction(isec->symbols[0], target, off, length, &newRelocs);
|
|
}
|
|
|
|
void EhRelocator::makeNegativePcRel(
|
|
uint64_t off, PointerUnion<Symbol *, InputSection *> target,
|
|
uint8_t length) {
|
|
createSubtraction</*Invert=*/true>(isec, target, off, length, &newRelocs);
|
|
}
|
|
|
|
void EhRelocator::commit() {
|
|
isec->relocs.insert(isec->relocs.end(), newRelocs.begin(), newRelocs.end());
|
|
}
|