package com.oracle.objectfile.macho;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
import com.oracle.objectfile.BuildDependency;
import com.oracle.objectfile.LayoutDecisionMap;
import com.oracle.objectfile.ObjectFile;
import com.oracle.objectfile.ObjectFile.Element;
import com.oracle.objectfile.ObjectFile.RelocationKind;
import com.oracle.objectfile.ObjectFile.RelocationMethod;
import com.oracle.objectfile.ObjectFile.RelocationRecord;
import com.oracle.objectfile.ObjectFile.Symbol;
import com.oracle.objectfile.SymbolTable;
import com.oracle.objectfile.io.AssemblyBuffer;
import com.oracle.objectfile.io.OutputAssembler;
import com.oracle.objectfile.macho.MachOObjectFile.MachOSection;
import com.oracle.objectfile.macho.MachOObjectFile.Segment64Command;
class MachORelocationElement extends MachOObjectFile.LinkEditElement {
private static int compareSectionThenOffset(RelocationInfo p, RelocationInfo q) {
if (!p.getRelocatedSection().equals(q.getRelocatedSection())) {
return p.getRelocatedSection().hashCode() - q.getRelocatedSection().hashCode();
}
return Math.toIntExact(p.getOffset() - q.getOffset());
}
private Map<RelocationInfo, RelocationInfo> infos = new TreeMap<>(MachORelocationElement::compareSectionThenOffset);
private Set<MachOSection> relocatedSections = new HashSet<>();
MachORelocationElement(Segment64Command segment) {
segment.getOwner().super("MachORelocationElement", segment);
assert segment.getOwner().relocs == null;
segment.getOwner().relocs = this;
}
public void add(RelocationInfo rec) {
if (infos.putIfAbsent(rec, rec) == null) {
relocatedSections.add(rec.getRelocatedSection());
}
}
public boolean relocatesSegment(Segment64Command seg) {
return seg.elementsInSegment.stream().anyMatch(e -> e instanceof MachOSection && relocatedSections.contains(e));
}
@Override
public byte[] getOrDecideContent(Map<Element, LayoutDecisionMap> alreadyDecided, byte[] contentHint) {
OutputAssembler out = AssemblyBuffer.createOutputAssembler(getOwner().getByteOrder());
for (RelocationInfo rec : infos.keySet()) {
rec.write(out, alreadyDecided);
}
assert getOrDecideSize(alreadyDecided, -1) == out.pos();
return out.getBlob();
}
@Override
public int getOrDecideSize(Map<Element, LayoutDecisionMap> alreadyDecided, int sizeHint) {
return infos.size() * encodedEntrySize();
}
@Override
public Iterable<BuildDependency> getDependencies(Map<Element, LayoutDecisionMap> decisions) {
return ObjectFile.minimalDependencies(decisions, this);
}
public int startIndexFor(MachOSection s) {
int i = 0;
for (RelocationInfo info : infos.keySet()) {
if (info.getRelocatedSection() == s) {
return i;
}
i++;
}
return -1;
}
public int encodedEntrySize() {
return RelocationInfo.getEncodedSize();
}
public int countFor(MachOSection s) {
return Math.toIntExact(infos.keySet().stream().filter(struct -> s == struct.getRelocatedSection()).count());
}
}
enum X86_64Reloc {
UNSIGNED,
SIGNED,
BRANCH,
GOT_LOAD,
GOT,
SUBTRACTOR,
SIGNED_1,
SIGNED_2,
SIGNED_4,
TLV;
public int getValue() {
return ordinal();
}
}
enum ARM64Reloc {
UNSIGNED,
SUBTRACTOR,
BRANCH26,
PAGE21,
PAGEOFF12,
GOT_LOAD_PAGE21,
GOT_LOAD_PAGEOFF12,
POINTER_TO_GOT,
TLVP_LOAD_PAGE21,
TLVP_LOAD_PAGEOFF12,
ADDEND;
public int getValue() {
return ordinal();
}
}
final class RelocationInfo implements RelocationRecord, RelocationMethod {
private final MachORelocationElement containingElement;
private final MachOSection relocatedSection;
private final RelocationKind kind;
private final int sectionOffset;
private final Symbol sym;
private final MachOSection targetSection;
private final byte log2length;
RelocationInfo(MachORelocationElement containingElement, MachOSection relocatedSection, int offset, int requestedLength, RelocationKind kind, String symbolName, boolean asLocalReloc) {
this.containingElement = containingElement;
this.relocatedSection = relocatedSection;
this.sectionOffset = offset;
if (requestedLength != 8 && requestedLength != 4 && requestedLength != 2 && requestedLength != 1) {
throw new IllegalArgumentException("Mach-O cannot represent relocation lengths other than {1,2,4,8} bytes");
}
this.log2length = (byte) ((requestedLength == 8) ? 3 : (requestedLength == 4) ? 2 : (requestedLength == 2) ? 1 : 0);
this.kind = kind;
SymbolTable symtab = relocatedSection.getOwner().getSymbolTable();
this.sym = symtab.getSymbol(symbolName);
assert !asLocalReloc || this.sym.isDefined();
this.targetSection = asLocalReloc ? (MachOSection) this.sym.getDefinedSection() : null;
}
public static int getEncodedSize() {
return 8;
}
public void write(OutputAssembler oa, @SuppressWarnings("unused") Map<Element, LayoutDecisionMap> alreadyDecided) {
int symbolNum;
if (isExtern()) {
symbolNum = relocatedSection.getOwner().getSymbolTable().indexOf(sym);
} else {
symbolNum = relocatedSection.getOwner().getSections().indexOf(sym.getDefinedSection());
assert sym.getDefinedOffset() == 0 : "Relocation for non-external symbol with section base offset != 0 not supported";
}
if (log2length < 0 || log2length >= 4) {
throw new IllegalArgumentException("length must be in {1,2,4,8} bytes, so log2length must be in [0,3]");
}
int startPos = oa.pos();
oa.write4Byte(sectionOffset);
int remainingWord = 0;
remainingWord |= symbolNum & 0x00ffffff;
remainingWord |= isPCRelative() ? (1 << 24) : 0;
remainingWord |= (log2length & 0x3) << 25;
remainingWord |= isExtern() ? (1 << 27) : 0;
remainingWord |= (getMachORelocationType() & 0xf) << 28;
oa.write4Byte(remainingWord);
assert oa.pos() - startPos == 8;
}
@Override
public long getOffset() {
return sectionOffset;
}
@Override
public Symbol getReferencedSymbol() {
return sym;
}
@Override
public boolean canUseExplicitAddend() {
return false;
}
@Override
public boolean canUseImplicitAddend() {
return true;
}
public MachOSection getRelocatedSection() {
return relocatedSection;
}
private boolean isExtern() {
return targetSection == null;
}
private boolean isPCRelative() {
switch (kind) {
case PC_RELATIVE_1:
case PC_RELATIVE_2:
case PC_RELATIVE_4:
case PC_RELATIVE_8:
case AARCH64_R_AARCH64_ADR_PREL_PG_HI21:
return true;
default:
return false;
}
}
private int getMachORelocationType() {
switch (getRelocatedSection().getOwner().cpuType) {
case X86_64:
switch (kind) {
case DIRECT_1:
case DIRECT_2:
case DIRECT_4:
case DIRECT_8:
return X86_64Reloc.UNSIGNED.getValue();
case PC_RELATIVE_1:
case PC_RELATIVE_2:
case PC_RELATIVE_4:
case PC_RELATIVE_8:
return X86_64Reloc.SIGNED.getValue();
default:
case UNKNOWN:
throw new IllegalArgumentException("unknown relocation kind: " + kind);
}
case ARM64:
switch (kind) {
case DIRECT_1:
case DIRECT_2:
case DIRECT_4:
case DIRECT_8:
return ARM64Reloc.UNSIGNED.getValue();
case AARCH64_R_AARCH64_ADR_PREL_PG_HI21:
return ARM64Reloc.PAGE21.getValue();
case AARCH64_R_AARCH64_LDST64_ABS_LO12_NC:
case AARCH64_R_AARCH64_LDST32_ABS_LO12_NC:
case AARCH64_R_AARCH64_LDST16_ABS_LO12_NC:
case AARCH64_R_AARCH64_LDST8_ABS_LO12_NC:
case AARCH64_R_AARCH64_ADD_ABS_LO12_NC:
return ARM64Reloc.PAGEOFF12.getValue();
default:
case UNKNOWN:
throw new IllegalArgumentException("unknown relocation kind: " + kind);
}
default:
throw new IllegalArgumentException("unknown relocation kind: " + kind);
}
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj != null && getClass() == obj.getClass()) {
RelocationInfo other = (RelocationInfo) obj;
return sectionOffset == other.sectionOffset && log2length == other.log2length && Objects.equals(containingElement, other.containingElement) &&
Objects.equals(getRelocatedSection(), other.getRelocatedSection()) && kind == other.kind &&
Objects.equals(sym, other.sym) && Objects.equals(targetSection, other.targetSection);
}
return false;
}
@Override
public int hashCode() {
return (((((containingElement.hashCode() * 31 + relocatedSection.hashCode()) * 31 + kind.hashCode()) * 31 +
sectionOffset) * 31 + sym.hashCode()) * 31 + targetSection.hashCode()) * 31 + log2length;
}
}