Nikita Popov 4522448cd1
[PowerPC][MC] Diagnose out of range branch fixups (#165859)
Currently, out-of-range branch fixups will be silently miscompiled. GNU
as will instead print an "operand out of range" error instead.

Check that the branch target fixups fit into the proper range and have
low zero bits. The implementation is inspired by SystemZ:
0ed8e66f88/llvm/lib/Target/SystemZ/MCTargetDesc/SystemZMCAsmBackend.cpp (L31)

(My actual interest here is not actually assembly usage, but LLVM
failing to use the correct branch kind for reasons I've not tracked down
yet. Currently this just silently truncates the branch target instead of
producing an error.)
2025-12-02 07:04:03 +01:00

319 lines
11 KiB
C++

//===-- PPCAsmBackend.cpp - PPC Assembler Backend -------------------------===//
//
// 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 "MCTargetDesc/PPCFixupKinds.h"
#include "MCTargetDesc/PPCMCAsmInfo.h"
#include "MCTargetDesc/PPCMCTargetDesc.h"
#include "llvm/BinaryFormat/ELF.h"
#include "llvm/BinaryFormat/MachO.h"
#include "llvm/MC/MCAsmBackend.h"
#include "llvm/MC/MCAssembler.h"
#include "llvm/MC/MCContext.h"
#include "llvm/MC/MCELFObjectWriter.h"
#include "llvm/MC/MCMachObjectWriter.h"
#include "llvm/MC/MCObjectWriter.h"
#include "llvm/MC/MCSubtargetInfo.h"
#include "llvm/MC/MCSymbolELF.h"
#include "llvm/MC/MCSymbolXCOFF.h"
#include "llvm/MC/MCValue.h"
#include "llvm/MC/TargetRegistry.h"
#include "llvm/Support/ErrorHandling.h"
using namespace llvm;
static uint64_t adjustFixupValue(MCContext &Ctx, const MCFixup &Fixup,
unsigned Kind, uint64_t Value) {
auto checkBrFixup = [&](unsigned Bits) {
int64_t SVal = int64_t(Value);
if ((Value & 3) != 0) {
Ctx.reportError(Fixup.getLoc(), "branch target not a multiple of four (" +
Twine(SVal) + ")");
return;
}
// Low two bits are not encoded.
if (!isIntN(Bits + 2, Value)) {
Ctx.reportError(Fixup.getLoc(), "branch target out of range (" +
Twine(SVal) + " not between " +
Twine(minIntN(Bits) * 4) + " and " +
Twine(maxIntN(Bits) * 4) + ")");
}
};
switch (Kind) {
default:
llvm_unreachable("Unknown fixup kind!");
case FK_Data_1:
case FK_Data_2:
case FK_Data_4:
case FK_Data_8:
case PPC::fixup_ppc_nofixup:
return Value;
case PPC::fixup_ppc_brcond14:
case PPC::fixup_ppc_brcond14abs:
checkBrFixup(14);
return Value & 0xfffc;
case PPC::fixup_ppc_br24:
case PPC::fixup_ppc_br24abs:
case PPC::fixup_ppc_br24_notoc:
checkBrFixup(24);
return Value & 0x3fffffc;
case PPC::fixup_ppc_half16:
return Value & 0xffff;
case PPC::fixup_ppc_half16ds:
case PPC::fixup_ppc_half16dq:
return Value & 0xfffc;
case PPC::fixup_ppc_pcrel32:
case PPC::fixup_ppc_imm32:
return Value & 0xffffffff;
case PPC::fixup_ppc_pcrel34:
case PPC::fixup_ppc_imm34:
return Value & 0x3ffffffff;
}
}
static unsigned getFixupKindNumBytes(unsigned Kind) {
switch (Kind) {
default:
llvm_unreachable("Unknown fixup kind!");
case FK_Data_1:
return 1;
case FK_Data_2:
case PPC::fixup_ppc_half16:
case PPC::fixup_ppc_half16ds:
case PPC::fixup_ppc_half16dq:
return 2;
case FK_Data_4:
case PPC::fixup_ppc_brcond14:
case PPC::fixup_ppc_brcond14abs:
case PPC::fixup_ppc_br24:
case PPC::fixup_ppc_br24abs:
case PPC::fixup_ppc_br24_notoc:
return 4;
case PPC::fixup_ppc_pcrel32:
case PPC::fixup_ppc_imm32:
case PPC::fixup_ppc_pcrel34:
case PPC::fixup_ppc_imm34:
case FK_Data_8:
return 8;
case PPC::fixup_ppc_nofixup:
return 0;
}
}
namespace {
class PPCAsmBackend : public MCAsmBackend {
protected:
Triple TT;
public:
PPCAsmBackend(const Target &T, const Triple &TT)
: MCAsmBackend(TT.isLittleEndian() ? llvm::endianness::little
: llvm::endianness::big),
TT(TT) {}
MCFixupKindInfo getFixupKindInfo(MCFixupKind Kind) const override;
void applyFixup(const MCFragment &, const MCFixup &Fixup,
const MCValue &Target, uint8_t *Data, uint64_t Value,
bool IsResolved) override;
bool shouldForceRelocation(const MCFixup &Fixup, const MCValue &Target) {
// If there is a @ specifier, unless it is optimized out (e.g. constant @l),
// force a relocation.
if (Target.getSpecifier())
return true;
MCFixupKind Kind = Fixup.getKind();
switch ((unsigned)Kind) {
default:
return false;
case PPC::fixup_ppc_br24:
case PPC::fixup_ppc_br24abs:
case PPC::fixup_ppc_br24_notoc:
// If the target symbol has a local entry point we must not attempt
// to resolve the fixup directly. Emit a relocation and leave
// resolution of the final target address to the linker.
if (const auto *A = Target.getAddSym()) {
if (getContext().isELF()) {
// The "other" values are stored in the last 6 bits of the second
// byte. The traditional defines for STO values assume the full byte
// and thus the shift to pack it.
unsigned Other = static_cast<const MCSymbolELF *>(A)->getOther() << 2;
if ((Other & ELF::STO_PPC64_LOCAL_MASK) != 0)
return true;
} else if (getContext().isXCOFF()) {
auto *S = static_cast<const MCSymbolXCOFF *>(A);
return !Target.isAbsolute() && S->isExternal() &&
S->getStorageClass() == XCOFF::C_WEAKEXT;
}
}
return false;
}
}
bool writeNopData(raw_ostream &OS, uint64_t Count,
const MCSubtargetInfo *STI) const override {
uint64_t NumNops = Count / 4;
for (uint64_t i = 0; i != NumNops; ++i)
support::endian::write<uint32_t>(OS, 0x60000000, Endian);
OS.write_zeros(Count % 4);
return true;
}
};
} // end anonymous namespace
MCFixupKindInfo PPCAsmBackend::getFixupKindInfo(MCFixupKind Kind) const {
// clang-format off
const static MCFixupKindInfo InfosBE[PPC::NumTargetFixupKinds] = {
// name offset bits flags
{"fixup_ppc_br24", 6, 24, 0},
{"fixup_ppc_br24_notoc", 6, 24, 0},
{"fixup_ppc_brcond14", 16, 14, 0},
{"fixup_ppc_br24abs", 6, 24, 0},
{"fixup_ppc_brcond14abs", 16, 14, 0},
{"fixup_ppc_half16", 0, 16, 0},
{"fixup_ppc_half16ds", 0, 14, 0},
{"fixup_ppc_pcrel32", 0, 32, 0},
{"fixup_ppc_imm32", 0, 32, 0},
{"fixup_ppc_pcrel34", 0, 34, 0},
{"fixup_ppc_imm34", 0, 34, 0},
{"fixup_ppc_nofixup", 0, 0, 0}};
const static MCFixupKindInfo InfosLE[PPC::NumTargetFixupKinds] = {
// name offset bits flags
{"fixup_ppc_br24", 2, 24, 0},
{"fixup_ppc_br24_notoc", 2, 24, 0},
{"fixup_ppc_brcond14", 2, 14, 0},
{"fixup_ppc_br24abs", 2, 24, 0},
{"fixup_ppc_brcond14abs", 2, 14, 0},
{"fixup_ppc_half16", 0, 16, 0},
{"fixup_ppc_half16ds", 2, 14, 0},
{"fixup_ppc_pcrel32", 0, 32, 0},
{"fixup_ppc_imm32", 0, 32, 0},
{"fixup_ppc_pcrel34", 0, 34, 0},
{"fixup_ppc_imm34", 0, 34, 0},
{"fixup_ppc_nofixup", 0, 0, 0}};
// clang-format on
// Fixup kinds from .reloc directive are like R_PPC_NONE/R_PPC64_NONE. They
// do not require any extra processing.
if (mc::isRelocation(Kind))
return {};
if (Kind < FirstTargetFixupKind)
return MCAsmBackend::getFixupKindInfo(Kind);
assert(Kind - FirstTargetFixupKind < PPC::NumTargetFixupKinds &&
"Invalid kind!");
return (Endian == llvm::endianness::little
? InfosLE
: InfosBE)[Kind - FirstTargetFixupKind];
}
void PPCAsmBackend::applyFixup(const MCFragment &F, const MCFixup &Fixup,
const MCValue &TargetVal, uint8_t *Data,
uint64_t Value, bool IsResolved) {
// In PPC64 ELFv1, .quad .TOC.@tocbase in the .opd section is expected to
// reference the null symbol.
auto Target = TargetVal;
if (Target.getSpecifier() == PPC::S_TOCBASE)
Target.setAddSym(nullptr);
if (IsResolved && shouldForceRelocation(Fixup, Target))
IsResolved = false;
if (!IsResolved)
Asm->getWriter().recordRelocation(F, Fixup, Target, Value);
MCFixupKind Kind = Fixup.getKind();
if (mc::isRelocation(Kind))
return;
Value = adjustFixupValue(getContext(), Fixup, Kind, Value);
if (!Value)
return; // Doesn't change encoding.
unsigned NumBytes = getFixupKindNumBytes(Kind);
// For each byte of the fragment that the fixup touches, mask in the bits
// from the fixup value. The Value has been "split up" into the appropriate
// bitfields above.
for (unsigned i = 0; i != NumBytes; ++i) {
unsigned Idx = Endian == llvm::endianness::little ? i : (NumBytes - 1 - i);
Data[i] |= uint8_t((Value >> (Idx * 8)) & 0xff);
}
}
// FIXME: This should be in a separate file.
namespace {
class ELFPPCAsmBackend : public PPCAsmBackend {
public:
ELFPPCAsmBackend(const Target &T, const Triple &TT) : PPCAsmBackend(T, TT) {}
std::unique_ptr<MCObjectTargetWriter>
createObjectTargetWriter() const override {
uint8_t OSABI = MCELFObjectTargetWriter::getOSABI(TT.getOS());
bool Is64 = TT.isPPC64();
return createPPCELFObjectWriter(Is64, OSABI);
}
std::optional<MCFixupKind> getFixupKind(StringRef Name) const override;
};
class XCOFFPPCAsmBackend : public PPCAsmBackend {
public:
XCOFFPPCAsmBackend(const Target &T, const Triple &TT)
: PPCAsmBackend(T, TT) {}
std::unique_ptr<MCObjectTargetWriter>
createObjectTargetWriter() const override {
return createPPCXCOFFObjectWriter(TT.isArch64Bit());
}
};
} // end anonymous namespace
std::optional<MCFixupKind>
ELFPPCAsmBackend::getFixupKind(StringRef Name) const {
if (TT.isOSBinFormatELF()) {
unsigned Type;
if (TT.isPPC64()) {
Type = llvm::StringSwitch<unsigned>(Name)
#define ELF_RELOC(X, Y) .Case(#X, Y)
#include "llvm/BinaryFormat/ELFRelocs/PowerPC64.def"
#undef ELF_RELOC
.Case("BFD_RELOC_NONE", ELF::R_PPC64_NONE)
.Case("BFD_RELOC_16", ELF::R_PPC64_ADDR16)
.Case("BFD_RELOC_32", ELF::R_PPC64_ADDR32)
.Case("BFD_RELOC_64", ELF::R_PPC64_ADDR64)
.Default(-1u);
} else {
Type = llvm::StringSwitch<unsigned>(Name)
#define ELF_RELOC(X, Y) .Case(#X, Y)
#include "llvm/BinaryFormat/ELFRelocs/PowerPC.def"
#undef ELF_RELOC
.Case("BFD_RELOC_NONE", ELF::R_PPC_NONE)
.Case("BFD_RELOC_16", ELF::R_PPC_ADDR16)
.Case("BFD_RELOC_32", ELF::R_PPC_ADDR32)
.Default(-1u);
}
if (Type != -1u)
return static_cast<MCFixupKind>(FirstLiteralRelocationKind + Type);
}
return std::nullopt;
}
MCAsmBackend *llvm::createPPCAsmBackend(const Target &T,
const MCSubtargetInfo &STI,
const MCRegisterInfo &MRI,
const MCTargetOptions &Options) {
const Triple &TT = STI.getTargetTriple();
if (TT.isOSBinFormatXCOFF())
return new XCOFFPPCAsmBackend(T, TT);
return new ELFPPCAsmBackend(T, TT);
}