com.oracle.objectfile.macho.MachOObjectFile Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of objectfile Show documentation
Show all versions of objectfile Show documentation
SubstrateVM object file writing library
The newest version!
/*
* Copyright (c) 2013, 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.oracle.objectfile.macho;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.graalvm.nativeimage.ImageSingletons;
import org.graalvm.nativeimage.Platform;
import com.oracle.objectfile.BasicProgbitsSectionImpl;
import com.oracle.objectfile.BuildDependency;
import com.oracle.objectfile.ElementImpl;
import com.oracle.objectfile.ElementList;
import com.oracle.objectfile.LayoutDecision;
import com.oracle.objectfile.LayoutDecisionMap;
import com.oracle.objectfile.ObjectFile;
import com.oracle.objectfile.SymbolTable;
import com.oracle.objectfile.io.AssemblyBuffer;
import com.oracle.objectfile.io.OutputAssembler;
/**
* Models a Mach-O relocatable object file.
*/
public final class MachOObjectFile extends ObjectFile {
/**
* Mach-O header representation. Note that this header only exists to participate in the
* Element-based build process. File-level state belongs in the MachOObjectFile class.
*/
private static final int MAGIC = 0xfeedfacf;
private static final int CIGAM = 0xcffaedfe;
private static final ByteOrder nativeOrder = ByteOrder.nativeOrder();
private static final ByteOrder oppositeOrder = (nativeOrder == ByteOrder.BIG_ENDIAN) ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN;
final MachOCpuType cpuType;
final int cpuSubType;
private final MachOHeader header;
private ByteOrder fileByteOrder;
MachORelocationElement relocs;
/**
* Create an empty Mach-O object file.
*/
public MachOObjectFile(int pageSize) {
this(pageSize, MachOCpuType.from(ImageSingletons.lookup(Platform.class).getArchitecture()));
}
public MachOObjectFile(int pageSize, MachOCpuType cpuType) {
super(pageSize);
this.cpuType = cpuType;
switch (cpuType) {
case X86_64:
cpuSubType = 3;
break;
default:
cpuSubType = 0;
}
header = new MachOHeader("MachOHeader");
setByteOrder(ByteOrder.nativeOrder());
// Create the boilerplate segment and sections within it.
Segment64Command segment = new Segment64Command("MachOUnnamedSegment", getUnnamedSegmentName());
// Give it full permission
segment.initprot = EnumSet.of(VMProt.READ, VMProt.WRITE, VMProt.EXECUTE);
segment.maxprot = EnumSet.of(VMProt.READ, VMProt.WRITE, VMProt.EXECUTE);
/*
* In what follows, keep to the order used by the native tools, just to rule out any
* difference as the source of non-functioning shared libraries.
*/
// Create a appropriate symbol tables etc (*also creates LinkEditSegment64Command *)
createSymbolTable();
assert getSymbolTable() != null;
// SOURCE_VERSION goes here
// LOAD_DYLIB goes here
LoadCommand functionStarts = new FunctionStartsCommand("MachOFunctionStartsCommand");
assert loadCommands.otherCommands.contains(functionStarts);
// DYLIB_CODE_SIGN_DRS goes here
}
@Override
public Format getFormat() {
return Format.MACH_O;
}
protected static String getUnnamedSegmentName() {
return "";
}
@Override
protected ElementList createElementList() {
/*
* Our ElementList has to account for the fact that segment ordering constraints section
* numbering, i.e. that sections are always numbered according to their position within the
* section and their segment's position within the load commands.
*/
return new MachOElementList();
}
@Override
public ByteOrder getByteOrder() {
return fileByteOrder;
}
@Override
public void setByteOrder(ByteOrder byteOrder) {
this.fileByteOrder = byteOrder;
}
@Override
protected int initialVaddr() {
// HACK: this (and the superclass version)
// is baking in *per-OS* knowledge, not just per-format knowledge...
// need to model OS [constraints] somehow.
return super.initialVaddr();
}
@Override
public int getWordSizeInBytes() {
return 8; // FIXME: add 32-bit support
}
@Override
public boolean shouldRecordDebugRelocations() {
return false;
}
@Override
public Symbol createDefinedSymbol(String name, Element baseSection, long position, int size, boolean isCode, boolean isGlobal) {
MachOSymtab symtab = (MachOSymtab) getOrCreateSymbolTable();
return symtab.newDefinedEntry(name, (MachOSection) baseSection, position, size, isGlobal, isCode);
}
@Override
public Symbol createUndefinedSymbol(String name, int size, boolean isCode) {
MachOSymtab symtab = (MachOSymtab) getOrCreateSymbolTable();
return symtab.newUndefinedEntry(name, isCode);
}
@Override
protected Segment64Command getOrCreateSegment(String segmentNameOrNull, String sectionName, boolean writable, boolean executable) {
final String segmentName = (segmentNameOrNull != null) ? segmentNameOrNull : getUnnamedSegmentName();
Segment64Command nonNullSegment = (Segment64Command) findSegmentByName(segmentName);
if (nonNullSegment != null) {
// we've found it; make sure it has the permission we need
if (nonNullSegment.isWritable() != writable) {
nonNullSegment.initprot.add(VMProt.WRITE);
nonNullSegment.maxprot.add(VMProt.WRITE);
}
if (nonNullSegment.isExecutable() != executable) {
nonNullSegment.initprot.add(VMProt.EXECUTE);
nonNullSegment.maxprot.add(VMProt.EXECUTE);
}
} else {
// create a segment
nonNullSegment = new Segment64Command(sectionName, segmentName);
nonNullSegment.initprot = EnumSet.of(VMProt.READ); // always give read permission
if (writable) {
nonNullSegment.initprot.add(VMProt.WRITE);
}
if (executable) {
nonNullSegment.initprot.add(VMProt.EXECUTE);
}
nonNullSegment.maxprot = nonNullSegment.initprot; // alias them, yessss.
assert loadCommands.otherCommands.contains(nonNullSegment);
}
assert nonNullSegment != null;
return nonNullSegment;
}
@Override
public MachOZeroFillSection newNobitsSection(Segment segment, String name, NobitsSectionImpl impl) {
assert segment != null && impl != null;
MachOZeroFillSection zeroFill = new MachOZeroFillSection(this, name, (Segment64Command) segment, impl);
impl.setElement(zeroFill);
return zeroFill;
}
@Override
public MachORegularSection newProgbitsSection(Segment segment, String name, int alignment, boolean writable, boolean executable, ProgbitsSectionImpl impl) {
assert segment != null;
EnumSet sectionFlags = EnumSet.noneOf(SectionFlag.class);
if (executable) {
sectionFlags.add(SectionFlag.SOME_INSTRUCTIONS);
// Magic flag for ld64 to actually identify it as section that contains code, see:
// https://github.com/apple-opensource/ld64/blob/e28c028b20af187a16a7161d89e91868a450cadc/src/ld/parsers/macho_relocatable_file.cpp#L4575-L4577
sectionFlags.add(SectionFlag.PURE_INSTRUCTIONS);
}
MachORegularSection regular = new MachORegularSection(this, name, alignment, (Segment64Command) segment, impl, sectionFlags);
impl.setElement(regular);
if (executable) {
((Segment64Command) segment).initprot.add(VMProt.EXECUTE);
}
if (writable) {
((Segment64Command) segment).initprot.add(VMProt.WRITE);
}
return regular;
}
@Override
public MachOUserDefinedSection newUserDefinedSection(Segment segment, String name, int alignment, ElementImpl impl) {
assert segment != null;
ElementImpl ourImpl;
if (impl == null) {
ourImpl = new BasicProgbitsSectionImpl((Section) null);
} else {
ourImpl = impl;
}
MachOUserDefinedSection userDefined = new MachOUserDefinedSection(this, name, alignment, (Segment64Command) segment, SectionType.REGULAR, ourImpl);
ourImpl.setElement(userDefined);
return userDefined;
}
private final class MachOElementList extends ElementList {
@Override
public int sectionIndexToElementIndex(int sectionIndex) {
/* naive for now */
int i = 0;
Iterator it = sectionsIterator();
while (it.hasNext()) {
Section s = it.next();
if (sectionIndex == i) {
return elements.indexOf(s);
}
++i;
}
return -1;
}
@Override
public Iterator sectionsIterator() {
return getSegments().stream()
.flatMap(segment -> ((Segment64Command) segment).elementsInSegment.stream())
.filter(element -> element instanceof Section)
.map(element -> (Section) element).iterator();
}
}
/**
* Mach-O file type.
*/
public enum FileType {
OBJECT(0x1),
EXECUTE(0x2),
FVMLIB(0x3),
PRELOAD(0x5),
DYLIB(0x6),
DYLINKER(0x7),
BUNDLE(0x8),
DYLIB_STUB(0x9),
DSYM(0xa),
KEXT_BUNDLE(0xb);
final int value;
FileType(int value) {
this.value = value;
}
}
enum Flag implements ValueEnum {
NOUNDEFS(0x1),
INCRLINK(0x2),
DYLDLINK(0x4),
BINDATLOAD(0x8),
PREBOUND(0x10),
SPLIT_SEGS(0x20),
LAZY_INIT(0x40),
TWOLEVEL(0x80),
FORCE_FLAT(0x100),
NOMULTIDEFS(0x200),
NOFIXPREBINDING(0x400),
PREBINDABLE(0x800),
ALLMODSBOUND(0x1000),
SUBSECTIONS_VIA_SYMBOLS(0x2000),
CANONICAL(0x4000);
private final int value;
Flag(int value) {
this.value = value;
}
@Override
public long value() {
return value;
}
}
static class HeaderStruct {
HeaderStruct(int magic, MachOCpuType cpuType, int cpuSubtype, int ncmds, int sizeOfCmds, int flags) {
this.magic = magic;
this.cpuType = cpuType;
this.cpuSubtype = cpuSubtype;
this.ncmds = ncmds;
this.sizeOfCmds = sizeOfCmds;
this.flags = flags;
}
HeaderStruct() {
}
private int magic;
private MachOCpuType cpuType;
private int cpuSubtype;
private int ncmds;
private int sizeOfCmds;
private int flags;
public void write(OutputAssembler out) {
int startPos = out.pos();
assert this.magic == MAGIC || this.magic == CIGAM;
out.setByteOrder(ByteOrder.nativeOrder());
out.write4Byte(this.magic);
out.setByteOrder(this.magic == MAGIC ? nativeOrder : oppositeOrder);
out.write4Byte(cpuType.toInt());
out.write4Byte(cpuSubtype);
out.write4Byte(FileType.OBJECT.value);
out.write4Byte(ncmds);
out.write4Byte(sizeOfCmds);
out.write4Byte(flags);
out.write4Byte(0); // reserved
assert out.pos() - startPos == HEADER_SIZE;
}
public static final int HEADER_SIZE = 32;
public int getWrittenSize() {
return HEADER_SIZE;
}
}
private LoadCommandList loadCommands = new LoadCommandList();
private class LoadCommandList implements Iterable {
LinkEditSegment64Command linkEditCommand; // or null; if present, always comes last!
List otherCommands = new ArrayList<>();
private int size() {
return otherCommands.size() + ((linkEditCommand == null) ? 0 : 1);
}
@Override
public Iterator iterator() {
return stream().iterator();
}
private Stream stream() {
if (linkEditCommand != null) {
return Stream.concat(otherCommands.stream(), Stream.of(linkEditCommand));
}
return otherCommands.stream();
}
private void add(LoadCommand arg) {
if (arg instanceof LinkEditSegment64Command) {
assert linkEditCommand == null : "cannot have more than one __LINKEDIT segment";
linkEditCommand = (LinkEditSegment64Command) arg;
} else {
otherCommands.add(arg);
}
}
}
public LoadCommand getLoadCommand(LoadCommandKind k) {
if (k == LoadCommandKind.SEGMENT_64) {
throw new IllegalArgumentException("Use getSegments() to get segments");
}
for (LoadCommand cmd : loadCommands) {
if (cmd.cmdKind == k) {
return cmd;
}
}
return null;
}
public Segment64Command getLinkEditSegment() {
final Segment64Command result = (Segment64Command) findSegmentByName(getUnnamedSegmentName());
return result;
}
public MachORelocationElement getRelocationElement() {
return relocs;
}
public MachORelocationElement getOrCreateRelocationElement() {
if (relocs == null) {
final Segment64Command containingSegment = getOrCreateSegment(getUnnamedSegmentName(), null, false, false);
relocs = new MachORelocationElement(containingSegment);
}
return relocs;
}
@Override
public Set getSegments() {
return loadCommands.stream().filter(loadCmd -> loadCmd instanceof Segment).map(loadCmd -> (Segment) loadCmd).collect(Collectors.toSet());
}
class MachOHeader extends Header {
MachOHeader(String name) {
super(name);
}
@Override
public byte[] getOrDecideContent(Map alreadyDecided, byte[] contentHint) {
OutputAssembler out = AssemblyBuffer.createOutputAssembler(ByteBuffer.allocate(65536).order(getOwner().getByteOrder())); // HACK
// int loadCommandsSizeInBytes = computeLoadCommandsSize(loadCommands);
out.skip((new HeaderStruct()).getWrittenSize());
out.align(8);
int loadCommandsSizeInBytes = 0;
for (LoadCommand cmd : loadCommands) {
loadCommandsSizeInBytes += (int) alreadyDecided.get(cmd).getDecidedValue(LayoutDecision.Kind.SIZE);
}
out.pushSeek(0);
new HeaderStruct(getOwner().getByteOrder() == nativeOrder ? MAGIC : CIGAM, cpuType, MachOObjectFile.this.cpuSubType, loadCommands.size(),
loadCommandsSizeInBytes, (int) ObjectFile.flagSetAsLong(MachOObjectFile.this.flags)).write(out);
out.pop();
return out.getBlob();
}
@Override
public int getOrDecideSize(Map alreadyDecided, int sizeHint) {
return HeaderStruct.HEADER_SIZE;
}
@Override
public Iterable getDependencies(Map decisions) {
/*
* Mach-O sections are always contained inside a segment, and must be emitted
* contiguously within that segment. Therefore, we create dependencies of two forms:
* linear OFFSET dependencies among sections inside the same segment (noting that
* segments are ordered), and (to prevent interleaving) from the first section of each
* segment to the last section of the previous segment.
*
* Note that Segments are (for us) ordered within the file, even though the contract
* inherited from ObjectFile specifies only Set. Also, note that the LINKEDIT segment
* must come *last*.
*
* Note also that for VADDR decisions, we must ensure that vaddrs and offsets go up
* together. This is because, unlike ELF, a single logical segment *must* be mapped in a
* single mmapping.
*/
HashSet deps = new HashSet<>();
Segment prevNonEmptySegment = null;
for (Segment s : getSegments()) {
Element prev = null;
for (Element e : s) {
assert !(e instanceof Header); // no headers here
if (prev != null) {
assert e != prev;
// create the within-segment predecessor offset dependency
deps.add(BuildDependency.createOrGet(decisions.get(e).getDecision(LayoutDecision.Kind.OFFSET), decisions.get(prev).getDecision(LayoutDecision.Kind.OFFSET)));
}
prev = e;
}
// only insert the interleaving-preventing dependency if we contain >0 entries
if (s.size() > 0 && prevNonEmptySegment != null) {
assert prevNonEmptySegment != s;
// create the between-segments predecessor offset dependency
deps.add(BuildDependency.createOrGet(decisions.get(s.get(0)).getDecision(LayoutDecision.Kind.OFFSET),
decisions.get(prevNonEmptySegment.get(prevNonEmptySegment.size() - 1)).getDecision(LayoutDecision.Kind.OFFSET)));
// same for vaddrs
if (s.get(0).isReferenceable() && prevNonEmptySegment.get(prevNonEmptySegment.size() - 1).isReferenceable()) {
deps.add(BuildDependency.createOrGet(decisions.get(s.get(0)).getDecision(LayoutDecision.Kind.VADDR),
decisions.get(prevNonEmptySegment.get(prevNonEmptySegment.size() - 1)).getDecision(LayoutDecision.Kind.VADDR)));
}
}
// only update the previous segment if the new one contains >0 sections
if (s.size() > 0) {
prevNonEmptySegment = s;
}
}
// our header content depends on the size of every load command
for (LoadCommand cmd : loadCommands) {
deps.add(BuildDependency.createOrGet(decisions.get(this).getDecision(LayoutDecision.Kind.CONTENT), decisions.get(cmd).getDecision(LayoutDecision.Kind.SIZE)));
}
// also declare that our offset depends on our size, to maintain the
// well-definedness of nextAvailableOffset
deps.add(BuildDependency.createOrGet(decisions.get(this).getDecision(LayoutDecision.Kind.OFFSET), decisions.get(this).getDecision(LayoutDecision.Kind.SIZE)));
/*
* The offset of every element inside a segment depends on the offset of all load
* commands. i.e. load commands come first :-)
*/
for (Segment seg : getSegments()) {
for (Element el : seg) {
for (LoadCommand cmd : loadCommands) {
deps.add(BuildDependency.createOrGet(decisions.get(el).getDecision(LayoutDecision.Kind.OFFSET), decisions.get(cmd).getDecision(LayoutDecision.Kind.OFFSET)));
}
}
}
/*
* Segment load commands must be kept in order, to avoid screwing with the section
* numbering.
*/
Segment64Command previousSegCmd = null;
for (Segment seg : getSegments()) {
Segment64Command segCmd = (Segment64Command) seg;
if (previousSegCmd != null) {
deps.add(BuildDependency.createOrGet(decisions.get(segCmd).getDecision(LayoutDecision.Kind.OFFSET), decisions.get(previousSegCmd).getDecision(LayoutDecision.Kind.OFFSET)));
}
previousSegCmd = segCmd;
}
/*
* Non-segment load commands should be output in the order they appear in our list.
* (This is just so that we can control the order of emission, for close reproduction of
* stock-generated binaries.)
*/
LoadCommand firstNonSegmentCmd = null;
LoadCommand previousCmd = null;
for (LoadCommand cmd : loadCommands) {
if (!(cmd instanceof Segment64Command)) {
if (previousCmd != null) {
deps.add(BuildDependency.createOrGet(decisions.get(cmd).getDecision(LayoutDecision.Kind.OFFSET), decisions.get(previousCmd).getDecision(LayoutDecision.Kind.OFFSET)));
} else {
firstNonSegmentCmd = cmd;
}
previousCmd = cmd;
}
}
/*
* Similarly, the first non-segment command comes after the last segment.
*/
if (firstNonSegmentCmd != null) {
assert previousSegCmd != null;
deps.add(BuildDependency.createOrGet(decisions.get(firstNonSegmentCmd).getDecision(LayoutDecision.Kind.OFFSET), decisions.get(previousSegCmd).getDecision(LayoutDecision.Kind.OFFSET)));
}
return deps;
}
}
static Map loadCommandKindsByValue = new HashMap<>();
static {
for (LoadCommandKind k : LoadCommandKind.values()) {
loadCommandKindsByValue.put((int) k.getValue(), k);
}
}
/**
* This enum defines an element for every defined value for 'cmd', i.e. every kind of load
* command. On Mac OS, these are defined in /usr/include/mach-o/loader.h. However, not all
* values are part of the published Mach-O spec.
*/
public enum LoadCommandKind {
/*
* To avoid any appearance of infringing Apple copyright, those not in the spec are replaced
* by 'unusedN', *except* for any that I found to be necessary for producing working Mach-O
* files. -srk
*/
unused0,
SEGMENT {
{
assert getValue() == 0x1;
}
},
SYMTAB {
{
assert getValue() == 0x2;
}
},
SYMSEG {
{
assert getValue() == 0x3;
}
},
THREAD {
{
assert getValue() == 0x4;
}
},
UNIXTHREAD {
{
assert getValue() == 0x5;
}
},
unused6 {
{
assert getValue() == 0x6;
}
},
unused7 {
{
assert getValue() == 0x7;
}
},
unused8 {
{
assert getValue() == 0x8;
}
},
unused9 {
{
assert getValue() == 0x9;
}
},
unuseda {
{
assert getValue() == 0xa;
}
},
DYSYMTAB {
{
assert getValue() == 0xb;
}
},
LOAD_DYLIB {
{
assert getValue() == 0xc;
}
},
ID_DYLIB {
{
assert getValue() == 0xd;
}
},
LOAD_DYLINKER {
{
assert getValue() == 0xe;
}
},
ID_DYLINKER {
{
assert getValue() == 0xf;
}
},
PREBOUND_DYLIB {
{
assert getValue() == 0x10;
}
},
ROUTINES {
{
assert getValue() == 0x11;
}
},
SUB_FRAMEWORK {
{
assert getValue() == 0x12;
}
},
SUB_UMBRELLA {
{
assert getValue() == 0x13;
}
},
SUB_CLIENT {
{
assert getValue() == 0x14;
}
},
SUB_LIBRARY {
{
assert getValue() == 0x15;
}
},
TWOLEVEL_HINTS {
{
assert getValue() == 0x16;
}
},
unused17 {
{
assert getValue() == 0x17;
}
},
unused18 {
{
assert getValue() == (0x18 | 0x80000000L);
}
@Override
public long getValue() {
return (super.getValue() | /* REQ_DYLD.getValue() */0x80000000L);
}
},
SEGMENT_64 {
{
assert getValue() == 0x19;
}
},
ROUTINES_64 {
{
assert getValue() == 0x1a;
}
},
UUID {
{
assert getValue() == 0x1b;
}
},
RPATH {
{
assert getValue() == (0x1c | 0x80000000L);
}
@Override
public long getValue() {
return super.getValue() | /* REQ_DYLD.getValue() */0x80000000L;
}
},
unused1d {
{
assert getValue() == 0x1d;
}
},
unused1e {
{
assert getValue() == 0x1e;
}
},
unused1f {
{
assert getValue() == (0x1f | 0x80000000L);
}
@Override
public long getValue() {
return super.getValue() | /* REQ_DYLD.getValue() */0x80000000L;
}
},
unused20 {
{
assert getValue() == 0x20;
}
},
unused21 {
{
assert getValue() == 0x21;
}
},
DYLD_INFO {
{
assert getValue() == 0x22;
}
},
unused23 {
{
assert getValue() == 0x23;
}
},
VERSION_MIN_MACOS {
{
assert getValue() == 0x24;
}
},
unused25 {
{
assert getValue() == 0x25;
}
},
FUNCTION_STARTS {
{
assert getValue() == 0x26;
}
},
unused27 {
{
assert getValue() == 0x27;
}
},
unused28 {
{
assert getValue() == (0x28 | 0x80000000L);
}
@Override
public long getValue() {
return super.getValue() | /* REQ_DYLD.getValue() */0x80000000L;
}
},
DATA_IN_CODE {
{
assert getValue() == 0x29;
}
},
unused2a {
{
assert getValue() == 0x2a;
}
},
unused2b {
{
assert getValue() == 0x2b;
}
},
REQ_DYLD {
@Override
public long getValue() {
return 0x80000000L;
}
},
DYLD_INFO_ONLY {
@Override
public long getValue() {
return DYLD_INFO.getValue() | /* REQ_DYLD.getValue() */0x80000000L;
}
};
public long getValue() {
return ordinal();
}
} // end enum Kind
/**
* Abstract super class of all load commands.
*/
public abstract class LoadCommand extends Header {
LoadCommandKind cmdKind; // 'cmd' in the struct definition
@SuppressWarnings("this-escape")
public LoadCommand(String name, LoadCommandKind k) {
super(name);
this.cmdKind = k;
// we add it as far towards the end of the list as we can, noting that we might
// have __LINKEDIT at the end, in which case we subtract 1
// -- unless we *are* a LinkEditSegment64Command, in which case we go at the end.
loadCommands.add(this);
}
protected abstract void writePayload(OutputAssembler out, Map alreadyDecided);
@Override
public MachOObjectFile getOwner() {
return (MachOObjectFile) super.getOwner();
}
@Override
public byte[] getOrDecideContent(Map alreadyDecided, byte[] contentHint) {
OutputAssembler out = AssemblyBuffer.createOutputAssembler(getByteOrder());
int startPos = out.pos();
out.write4Byte((int) cmdKind.getValue());
int sizePos = out.pos();
out.write4Byte(0); // placeholder for size
writePayload(out, alreadyDecided);
out.align(8);
int cmdSize = out.pos() - startPos;
out.pushSeek(sizePos);
out.write4Byte(cmdSize);
out.pop();
assert out.pos() == startPos + cmdSize;
return out.getBlob();
}
int sizeBeforePayload() {
return getWrittenSize(0);
}
/**
* Get the size of this load command, given a particular payload size. Subclasses can use
* this to compute the overall size. This allows them to avoid a dependency from content to
* size, which our default dependencies include. We use this in {@link SymtabCommand} to
* avoid creating cyclic dependencies.
*
* @param payloadSize the size on disk, in bytes, of the payload part of the load command
* @return the size on disk, in bytes, of the load command as a whole
*/
protected int getWrittenSize(int payloadSize) {
/*
* HACK: this is replicating logic from getOrDecideContent, but I can't figure out a
* nice way of avoiding it.
*/
OutputAssembler out = AssemblyBuffer.createOutputAssembler(getByteOrder());
int startPos = out.pos();
out.write4Byte((int) cmdKind.getValue());
// int sizePos = out.pos();
out.write4Byte(0); // placeholder for size
out.skip(payloadSize);
out.align(8);
return /* cmdSize = */out.pos() - startPos;
}
}
/**
* We override getHeader() since LoadCommands are also headers, so we do not have a unique
* Element satisfying instanceof Header. (For the reason why LoadCommands are headers, see
* com.oracle.svm.debug.sections.CustomRelocationSectionImpl.getDependencies().)
*/
@Override
public Header getHeader() {
return header;
}
/**
* UUID load command.
*/
public class UUIDCommand extends LoadCommand {
byte[] uuidbytes;
public UUIDCommand(String name) {
super(name, LoadCommandKind.UUID);
/*
* FIXME: this is a sketchy interpretation of the UUID v4 RFC.
*/
UUID randomUUID = UUID.randomUUID();
// we write the whole thing big-endianly
OutputAssembler oa = AssemblyBuffer.createOutputAssembler(ByteOrder.BIG_ENDIAN);
oa.write8Byte(randomUUID.getMostSignificantBits());
oa.write8Byte(randomUUID.getLeastSignificantBits());
assert oa.pos() == 16; // UUIDs are 16 bytes long
uuidbytes = oa.getBlob();
// FIXME: use nameUUIDFromBytes() instead, i.e. v3/v5 not v4
assert uuidbytes.length == 16;
}
@Override
protected void writePayload(OutputAssembler out, Map alreadyDecided) {
out.writeBlob(uuidbytes);
}
}
static class DylibStruct {
int stroff; // is lc_str in the spec, but ptr case is unused
int timestamp;
int currentVersion;
int compatibilityVersion;
public void write(OutputAssembler out) {
/*
* NOTE: this should actually be 64 bits, because it's an lc_str which is a union of
* uint32_t and char*. But mysteriously, the native tools only use 32 bits for this. So
* I will only use 32 bits too.
*/
out.write4Byte(stroff);
out.write4Byte(timestamp);
out.write4Byte(currentVersion);
out.write4Byte(compatibilityVersion);
}
DylibStruct(int stroff, int timestamp, int currentVersion, int compatibilityVersion) {
this.stroff = stroff;
this.timestamp = timestamp;
this.currentVersion = currentVersion;
this.compatibilityVersion = compatibilityVersion;
}
public int getWrittenSize() {
OutputAssembler oa = AssemblyBuffer.createOutputAssembler();
write(oa);
return oa.pos();
}
}
/**
* This abstract superclass models all load commands wrapping a struct dylib, namely
* {@link LoadDylibCommand} and {@link IDDylibCommand} (and, if we implement it,
* LoadWeakDylibCommand).
*
*/
public abstract class AbstractDylibCommand extends LoadCommand {
String libName = "blah.dylib"; // FIXME
// FIXME: other fields
public AbstractDylibCommand(String name, LoadCommandKind k, String libName) {
super(name, k);
this.libName = libName;
}
public AbstractDylibCommand(String name, LoadCommandKind k) {
super(name, k);
}
@Override
protected void writePayload(OutputAssembler out, Map alreadyDecided) {
/*
* Our payload is just our dylib struct followed by the string denoting our name. The
* first field of our dylib struct is the offset of the string into the load command,
* which is just the load command header length plus the size of the dylib struct.
*/
int loadCommandHeaderLength = getWrittenSize(0);
DylibStruct s = new DylibStruct(0, 0, 0, 0); // FIXME: real values please!
s.stroff = loadCommandHeaderLength + s.getWrittenSize();
s.write(out);
out.writeString(libName);
}
public void setLibName(String libName) {
this.libName = libName;
}
}
public class IDDylibCommand extends AbstractDylibCommand {
public IDDylibCommand(String name, String libName) {
super(name, LoadCommandKind.ID_DYLIB);
this.libName = libName;
}
public IDDylibCommand(String name) {
super(name, LoadCommandKind.ID_DYLIB);
}
}
/**
* Utility function to get the length of a long when ULEB128-encoded. We use this in
* FunctionStartsElement and also ExportTrie.
*
* @param value a long value
* @return the length in bytes of the ULEB128 encoding of value
*/
static int encodedLengthLEB128(long value) {
OutputAssembler dummy = AssemblyBuffer.createOutputAssembler();
dummy.writeLEB128(value);
return dummy.pos();
}
class VersionMinMacOSCommand extends LoadCommand {
VersionMinMacOSCommand(String name) {
super(name, LoadCommandKind.VERSION_MIN_MACOS);
}
@Override
protected void writePayload(OutputAssembler out, Map alreadyDecided) {
// FIXME: be smarter than just writing "10.07"
out.writeByte((byte) 0);
out.writeByte((byte) 7);
out.writeByte((byte) 10);
out.writeByte((byte) 0);
out.write4Byte(0);
}
}
public class LoadDylibCommand extends AbstractDylibCommand {
int timestamp;
int currentVersion;
int compatVersion;
public LoadDylibCommand(String name, String libname, int timestamp, int currentVersion, int compatVersion) {
super(name, LoadCommandKind.LOAD_DYLIB);
this.libName = libname;
this.timestamp = timestamp;
this.currentVersion = currentVersion;
this.compatVersion = compatVersion;
}
@Override
protected void writePayload(OutputAssembler out, Map alreadyDecided) {
int loadCommandHeaderSize = getWrittenSize(0);
DylibStruct s = new DylibStruct(0, timestamp, currentVersion, compatVersion);
// string offset is command header size + dylib struct size
s.stroff = loadCommandHeaderSize + s.getWrittenSize();
s.write(out);
out.writeString(libName);
}
}
public class RPathCommand extends LoadCommand {
String dirname;
public RPathCommand(String name, String dirname) {
super(name, LoadCommandKind.RPATH);
this.dirname = dirname;
}
@Override
protected void writePayload(OutputAssembler out, Map alreadyDecided) {
int loadCommandHeaderSize = getWrittenSize(0);
/* the payload is simply an lc_str followed by the null-terminated string bytes */
int stroff = loadCommandHeaderSize + 4; // 32-bit offset from load command start to
// string start
out.write4Byte(stroff);
out.writeString(dirname);
}
}
class FunctionStartsCommand extends LoadCommand {
FunctionStartsElement el;
FunctionStartsCommand(String name) {
super(name, LoadCommandKind.FUNCTION_STARTS);
el = new FunctionStartsElement("MachOFunctionStartsElement");
}
@Override
public Iterable getDependencies(Map decisions) {
HashSet deps = ObjectFile.minimalDependencies(decisions, this);
LayoutDecision ourContent = decisions.get(this).getDecision(LayoutDecision.Kind.CONTENT);
deps.add(BuildDependency.createOrGet(ourContent, decisions.get(el).getDecision(LayoutDecision.Kind.OFFSET)));
deps.add(BuildDependency.createOrGet(ourContent, decisions.get(el).getDecision(LayoutDecision.Kind.SIZE)));
return deps;
}
@Override
protected void writePayload(OutputAssembler out, Map alreadyDecided) {
// our payload is simply the offset and size of our element
int elOffset = (int) alreadyDecided.get(el).getDecidedValue(LayoutDecision.Kind.OFFSET);
int elSize = (int) alreadyDecided.get(el).getDecidedValue(LayoutDecision.Kind.SIZE);
out.write4Byte(elOffset);
out.write4Byte(elSize);
}
@Override
public int getOrDecideSize(Map alreadyDecided, int sizeHint) {
return getWrittenSize(8); // 4 + 4 bytes
}
}
class FunctionStartsElement extends LinkEditElement {
/*
* This is not documented in the Mach-O spec, but we generate it so we can reproduce a
* simple test Mach-O file generated by the system's ld. It seems to consist of an array of
* offsets of function entry points from the previous function entry point in the file,
* ULEB128-encoded. We use the symtab to generate this array.
*/
/* This element is a zero-terminated sequence of ULEB128s. */
FunctionStartsElement(String name) {
super(name, getLinkEditSegment()); // adds us to the link edit segment
}
@Override
public byte[] getOrDecideContent(Map alreadyDecided, byte[] contentHint) {
OutputAssembler out = AssemblyBuffer.createOutputAssembler(getOwner().getByteOrder());
TreeSet fileOffsets = new TreeSet<>(); // merge duplicates (aliased symbols)
for (Symbol sym : symbolsOfInterest()) {
Section s = sym.getDefinedSection();
assert s != null;
int sectionOffset = (int) alreadyDecided.get(s).getDecidedValue(LayoutDecision.Kind.OFFSET);
fileOffsets.add(sectionOffset + (int) sym.getDefinedOffset());
}
Integer previousOffset = null;
for (Integer i : fileOffsets) {
if (previousOffset == null) {
// write the offset from starting fileoff of the text segment
// -- *which should be 0*!
Segment textSegment = null;
for (Segment s : getSegments()) {
if (s.getName().equals("__TEXT")) {
textSegment = s;
break;
}
}
if (textSegment == null) {
// no text segment, so our content is empty
break;
}
out.writeLEB128(i); // HACK: assuming text segment begins at fileoff 0!
} else {
out.writeLEB128(i - previousOffset);
}
}
out.writeLEB128(0);
// zero-pad to overapproximated size
// FIXME: this creates quite a bit of unnecessary padding
int overapproximation = overapproximateSize();
assert out.pos() <= overapproximation;
out.skip(overapproximation - out.pos());
return out.getBlob();
}
private static final int BIGGEST_INTER_FUNCTION_GAP = 65536;
private int overapproximateSize() {
int size = 1; // for terminator
for (Symbol sym : symbolsOfInterest()) {
Section s = sym.getDefinedSection();
assert s != null;
int offsetEncodedLength = MachOObjectFile.encodedLengthLEB128(BIGGEST_INTER_FUNCTION_GAP);
size += offsetEncodedLength;
}
return size;
}
@Override
public int getOrDecideSize(java.util.Map alreadyDecided, int sizeHint) {
Object decidedContent = alreadyDecided.get(this).getDecidedValue(LayoutDecision.Kind.CONTENT);
assert decidedContent != null;
return ((byte[]) decidedContent).length;
}
@Override
public Iterable getDependencies(Map decisions) {
// our content depends on the offset of every section we're going to reference
HashSet deps = ObjectFile.defaultDependencies(decisions, this);
ArrayList requiredOffsets = new ArrayList<>();
for (LoadCommand c : loadCommands) {
if (c instanceof SymtabCommand) {
SymtabCommand syms = (SymtabCommand) c;
for (Symbol sym : syms.symtab) {
if (sym.isDefined() && sym.isFunction() && !sym.isAbsolute()) {
Section s = sym.getDefinedSection();
assert s != null;
requiredOffsets.add(s);
}
}
}
}
LayoutDecision ourContent = decisions.get(this).getDecision(LayoutDecision.Kind.CONTENT);
for (Section s : requiredOffsets) {
deps.add(BuildDependency.createOrGet(ourContent, decisions.get(s).getDecision(LayoutDecision.Kind.OFFSET)));
}
return deps;
}
private List symbolsOfInterest() {
List ofInterest = new ArrayList<>();
for (LoadCommand c : loadCommands) {
if (c instanceof SymtabCommand) {
SymtabCommand syms = (SymtabCommand) c;
for (Symbol sym : syms.symtab) {
if (sym.isDefined() && sym.isFunction() && !sym.isAbsolute()) {
ofInterest.add(sym);
}
}
}
}
return ofInterest;
}
}
class DataInCodeElement extends LinkEditElement {
class EntryStruct {
int fileOffset;
short length;
short entryKind;
int getWrittenSize() {
return 8; // the size of the fields above
}
void write(OutputAssembler oa) {
oa.write4Byte(fileOffset);
oa.write2Byte(length);
oa.write2Byte(entryKind);
}
}
DataInCodeElement(String name) {
super(name, getLinkEditSegment()); // adds us to the link edit segment
}
@Override
public Iterable getDependencies(Map decisions) {
HashSet deps = ObjectFile.minimalDependencies(decisions, this);
// our content (but not our size) depends on the offsets and sizes of every text section
LayoutDecision ourContent = decisions.get(this).getDecision(LayoutDecision.Kind.CONTENT);
for (Section s : getSections()) {
MachOSection ms = (MachOSection) s;
if (ms.flags.contains(SectionFlag.SOME_INSTRUCTIONS)) {
deps.add(BuildDependency.createOrGet(ourContent, decisions.get(s).getDecision(LayoutDecision.Kind.OFFSET)));
deps.add(BuildDependency.createOrGet(ourContent, decisions.get(s).getDecision(LayoutDecision.Kind.SIZE)));
}
}
return deps;
}
@Override
public int getOrDecideSize(Map alreadyDecided, int sizeHint) {
// our size is fixed: one entry for every text section
int count = 0;
for (Section s : getSections()) {
MachOSection ms = (MachOSection) s;
if (ms.flags.contains(SectionFlag.SOME_INSTRUCTIONS)) {
++count;
}
}
return count * (new EntryStruct()).getWrittenSize();
}
@Override
public byte[] getOrDecideContent(Map alreadyDecided, byte[] contentHint) {
OutputAssembler out = AssemblyBuffer.createOutputAssembler(getOwner().getByteOrder());
ArrayList decisionsOfInterest = new ArrayList<>();
for (Section s : getSections()) {
MachOSection ms = (MachOSection) s;
if (ms.flags.contains(SectionFlag.SOME_INSTRUCTIONS)) {
decisionsOfInterest.add(alreadyDecided.get(s).getDecision(LayoutDecision.Kind.OFFSET));
}
}
// sort these sections by their decided offset
Collections.sort(decisionsOfInterest, new IntegerDecisionComparator(false));
// we should not have any undecideds!
assert decisionsOfInterest.size() == 0 || decisionsOfInterest.get(0).isTaken();
EntryStruct ent = new EntryStruct();
for (int i = 0; i < decisionsOfInterest.size(); ++i) {
LayoutDecision decision = decisionsOfInterest.get(i);
ent.fileOffset = (int) decision.getValue();
int fileSize = (int) alreadyDecided.get(decision.getElement()).getDecidedValue(LayoutDecision.Kind.SIZE);
int sectionEndInFile = ent.fileOffset + fileSize;
Integer nextOffset = (i + 1 < decisionsOfInterest.size()) ? (int) decisionsOfInterest.get(i + 1).getValue() : null;
int nextPageBoundary = (sectionEndInFile % getPageSize()) == 0 ? sectionEndInFile : (((sectionEndInFile >> getPageSizeShift()) + 1) << getPageSizeShift());
ent.length = (short) (nextOffset == null ? nextPageBoundary : Math.min(nextPageBoundary, nextOffset));
ent.entryKind = (short) 0; // FIXME
ent.write(out);
}
return out.getBlob();
}
}
class DataInCodeCommand extends LoadCommand {
DataInCodeElement el;
DataInCodeCommand(String name) {
super(name, LoadCommandKind.DATA_IN_CODE);
this.el = new DataInCodeElement(name);
}
/*
* This is not in the Mach-O spec, but we include it in order to reproduce a simple test
* dylib that the native ld generates. The payload -- which is a LinkEditElement -- seems to
* consist of file offsets of parts in a text *segment* that are not in fact executable.
* Each is described by a data-in-code-entry. As a complete HACK, we generate a single entry
* per text section which covers everything between the end of a text section and the page
* boundary OR another text section, whichever comes first.
*/
@Override
public Iterable getDependencies(Map decisions) {
HashSet deps = ObjectFile.minimalDependencies(decisions, this);
// our content (but not our size) depends on the offset and size
// of the corresponding element
LayoutDecision ourContent = decisions.get(this).getDecision(LayoutDecision.Kind.CONTENT);
LayoutDecision elOffset = decisions.get(el).getDecision(LayoutDecision.Kind.OFFSET);
LayoutDecision elSize = decisions.get(el).getDecision(LayoutDecision.Kind.SIZE);
deps.add(BuildDependency.createOrGet(ourContent, elOffset));
deps.add(BuildDependency.createOrGet(ourContent, elSize));
return deps;
}
@Override
protected void writePayload(OutputAssembler out, Map alreadyDecided) {
out.write4Byte((int) alreadyDecided.get(el).getDecidedValue(LayoutDecision.Kind.OFFSET));
out.write4Byte((int) alreadyDecided.get(el).getDecidedValue(LayoutDecision.Kind.SIZE));
}
@Override
public int getOrDecideSize(Map alreadyDecided, int sizeHint) {
return getWrittenSize(8); // i.e. two words
}
}
public enum SectionType {
// IMPORTANT: these all have values matching their ordinal()
REGULAR,
ZEROFILL,
LITERALS_CSTRING,
LITERALS_4BYTE,
LITERALS_8BYTE,
LITERALS_POINTER,
NON_LAZY_SYMBOL_POINTERS,
LAZY_SYMBOL_POINTERS,
SYMBOL_STUBS,
MOD_INIT_FUNC_POINTERS,
MOD_TERM_FUNC_POINTERS,
COALESCED,
GB_ZEROFILL;
static SectionType fromFlags(int flags) {
return values()[flags & 0xff];
}
int getValue() {
return ordinal();
}
}
public enum SectionFlag implements ValueEnum {
LOC_RELOC(0x00000100),
EXT_RELOC(0x00000200),
SOME_INSTRUCTIONS(0x00000400),
DEBUG(0x02000000),
SELF_MODIFYING_CODE(0x04000000),
LIVE_SUPPORT(0x08000000),
NO_DEAD_STRIP(0x10000000),
STRIP_STATIC_SYMS(0x20000000),
NO_TOC(0x40000000),
PURE_INSTRUCTIONS(0x80000000);
private final int value;
SectionFlag(int value) {
this.value = value;
}
@Override
public long value() {
return value;
}
}
public abstract class MachOSection extends ObjectFile.Section {
/*- We have no fields except type & flags! Mach-O section64 struct's fields are
* modelled as follows:
* sectname: in the ObjectFile's element name map
* segname: explicitly if relocatable file, else in the ObjectFile's segments list
* addr: decided during the build process
* size: for progbits, the byte[]'s size; for nobits, by getMemSize()
* offset: decided during the build process
* align: in Element
* reloff: offset into the relocation section contents
* nreloc: ditto
* flags: we DO have this one
* reserved1, reserved2: saved for a "symbol stub section" subclass, if we need it
*/
SectionType type;
EnumSet flags;
Segment64Command segment;
String destinationSegmentName;
@Override
public boolean isLoadable() {
if (getImpl() == this) {
return true;
}
return getImpl().isLoadable();
}
@Override
public boolean isReferenceable() {
if (getImpl() == this) {
return isLoadable();
}
return getImpl().isReferenceable();
}
@SuppressWarnings("this-escape")
public MachOSection(String name, int alignment, Segment64Command segment, SectionType t, EnumSet flags) {
super(name, alignment);
if (name.length() > 16) {
throw new IllegalArgumentException("Mach-O section names may not be longer than 16 characters");
}
this.type = SectionType.REGULAR;
assert t.equals(this.type);
this.flags = flags;
/*
* Q. Where do we add the section to the segment? A. Before any non-Section elements,
* but after any other Sections. Q. Why? A. To avoid cyclic dependencies in the
* relocatable case. In relocatable files, everything goes in a single segment. This
* includes both data sections *and* link-edit elements. At least one link-edit element,
* FunctionStartsElement, requires the offset of text sections to be known before its
* own size and offset can be calculated. If we put any text section later than it in
* the segment, we'd get a cyclic dependency: an OFFSET->OFFSET back-edge within the
* segment as usual, and a *forward*-edge because the FunctionStartsElement's offset
* depends on the text section's offset.
*/
int firstNonSectionPosition = 0;
while (firstNonSectionPosition < segment.size() && segment.get(firstNonSectionPosition) instanceof Section) {
++firstNonSectionPosition;
}
segment.add(firstNonSectionPosition, this);
this.segment = segment;
/*
* Guess a destination segment name, if we're relocatable. This is a bit of a HACK right
* now. It also duplicates SectionName knowledge. FIXME: why not just ask SectionName?
*/
if (name.contains("debug")) {
destinationSegmentName = "__DWARF";
/*
* FIXME: set the DEBUG flag on this section. Unfortunately, this currently breaks
* debugging: on OS X, the linker intentionally strips debug sections because
* debuggers are expected to retrieve them from the original object files or from a
* debug info archive. We should conform to this by creating a debug info archive
* using dsymutil(1), which would also reduce the size of the linked binary.
* However, attempts to implement this as in an extra step after linking has failed,
* which likely means that more other stuff needs to be fixed beforehand.
*/
// flags.add(SectionFlag.DEBUG);
} else if (flags.contains(
SectionFlag.SOME_INSTRUCTIONS) /* || name.equals("__rodata") */) {
/*
* HACK: __rodata normally goes in __TEXT. However, SubstrateVM's __rodata sections
* currently includes relocatable information, namely pointers into the __data
* section. The Darwin linker complains about these when trying to build shared
* libraries out of relocatabl object files, giving error messages like
*
* ld: illegal text-relocation to ___data in
* /.../images/com_oracle_svm_test_jdk_HelloWorld_format.dylib.o from ___rodata in
* /.../images/com_oracle_svm_test_jdk_HelloWorld_format.dylib.o for architecture
* x86_64
*
* so we hack around it by keeping __rodata in __DATA.
*
* This could be fixed more cleanly by splitting __rodata into "pure" data not
* needing fix-ups, which can go in __TEXT, and "dirty" fixup-requiring pointers,
* which can stay in __DATA.
*/
destinationSegmentName = "__TEXT";
} else {
destinationSegmentName = "__DATA";
}
}
public Segment getSegment() {
return segment;
}
public void setDestinationSegmentName(String dest) {
this.destinationSegmentName = dest;
}
@Override
public MachOObjectFile getOwner() {
return (MachOObjectFile) super.getOwner();
}
}
/**
* Symtab load command.
*/
public class SymtabCommand extends LoadCommand {
MachOSymtab symtab;
public SymtabCommand(String name, MachOSymtab symtab) {
super(name, LoadCommandKind.SYMTAB);
this.symtab = symtab;
}
@Override
protected void writePayload(OutputAssembler out, Map alreadyDecided) {
int symtabOffset = (int) alreadyDecided.get(symtab).getDecidedValue(LayoutDecision.Kind.OFFSET);
int symtabEntriesCount = symtab.getEntryCount();
int strtabOffset = (int) alreadyDecided.get(symtab.strtab).getDecidedValue(LayoutDecision.Kind.OFFSET);
int strtabSize = (int) alreadyDecided.get(symtab.strtab).getDecidedValue(LayoutDecision.Kind.SIZE);
writePayloadFields(out, symtabOffset, symtabEntriesCount, strtabOffset, strtabSize);
}
private void writePayloadFields(OutputAssembler out, int symtabOffset, int symtabEntriesCount, int strtabOffset, int strtabSize) {
out.write4Byte(symtabOffset);
out.write4Byte(symtabEntriesCount);
out.write4Byte(strtabOffset);
out.write4Byte(strtabSize);
}
private int getPayloadWrittenSize() {
OutputAssembler oa = AssemblyBuffer.createOutputAssembler();
writePayloadFields(oa, 0, 0, 0, 0);
return oa.pos();
}
@Override
public int getOrDecideSize(Map alreadyDecided, int sizeHint) {
return getWrittenSize(getPayloadWrittenSize());
}
@Override
public Iterable getDependencies(Map decisions) {
HashSet deps = ObjectFile.minimalDependencies(decisions, this);
// our content depends on the offset and size of strtab, and offset of symtab
LayoutDecision ourContent = decisions.get(this).getDecision(LayoutDecision.Kind.CONTENT);
LayoutDecision strtabSize = decisions.get(symtab.strtab).getDecision(LayoutDecision.Kind.SIZE);
LayoutDecision strtabOffset = decisions.get(symtab.strtab).getDecision(LayoutDecision.Kind.OFFSET);
LayoutDecision symtabOffset = decisions.get(symtab).getDecision(LayoutDecision.Kind.OFFSET);
deps.add(BuildDependency.createOrGet(ourContent, strtabSize));
deps.add(BuildDependency.createOrGet(ourContent, strtabOffset));
deps.add(BuildDependency.createOrGet(ourContent, symtabOffset));
return deps;
}
}
/**
* Symtab load command.
*/
public class DySymtabCommand extends LoadCommand {
MachOSymtab symtab;
public DySymtabCommand(String name, MachOSymtab symtab) {
super(name, LoadCommandKind.DYSYMTAB);
this.symtab = symtab;
}
private static final int PAYLOAD_SIZE = 18 * 4; // 18 32-bit entries
@Override
protected void writePayload(OutputAssembler out, Map alreadyDecided) {
int startPos = out.pos();
out.write4Byte(/* ilocalsym */symtab.firstLocal());
out.write4Byte(/* nlocalsym */symtab.nLocals());
out.write4Byte(/* iextdefsym */symtab.firstExtDef());
out.write4Byte(/* nextdefsym */symtab.nExtDef());
out.write4Byte(/* iundefsym */symtab.firstUndef());
out.write4Byte(/* nundefsym */symtab.nUndef());
out.write4Byte(/* tocoff */0);
out.write4Byte(/* ntoc */0);
out.write4Byte(/* modtaboff */0);
out.write4Byte(/* nmodtab */0);
out.write4Byte(/* extrefsymoff */0);
out.write4Byte(/* nextrefsyms */0);
out.write4Byte(/* indirectsymoff */0);
out.write4Byte(/* nindirectsyms */0);
out.write4Byte(/* extreloff */0);
out.write4Byte(/* nextrel */0);
out.write4Byte(/* localreloff */0);
out.write4Byte(/* nlocrel */0);
assert out.pos() == startPos + PAYLOAD_SIZE;
}
@Override
public Iterable getDependencies(Map decisions) {
HashSet deps = ObjectFile.minimalDependencies(decisions, this);
return deps;
}
@Override
public int getOrDecideSize(Map alreadyDecided, int sizeHint) {
return getWrittenSize(PAYLOAD_SIZE);
}
}
public enum VMProt implements ValueEnum {
READ(0x01),
WRITE(0x02),
EXECUTE(0x04);
private final int value;
VMProt(int value) {
this.value = value;
}
@Override
public long value() {
return value;
}
}
/**
* Section directory entries as held by a Segment64 load command.
*/
public static class SectionInfoStruct {
public static final int DEFAULT_SIZE = 80; // data members below plus padding
String sectName;
String segName;
long addr;
long size;
int offset;
int align;
int reloff;
int nreloc;
int flags;
int reserved1;
int reserved2;
public SectionInfoStruct(String sectName, String segName, long addr, long size, int offset, int align, int reloff, int nreloc, int flags, int reserved1, int reserved2) {
super();
this.sectName = sectName;
this.segName = segName;
this.addr = addr;
this.size = size;
this.offset = offset;
this.align = align;
this.reloff = reloff;
this.nreloc = nreloc;
this.flags = flags;
this.reserved1 = reserved1;
this.reserved2 = reserved2;
}
public void write(OutputAssembler db) {
db.writeStringPadded(sectName, 16);
db.writeStringPadded(segName, 16);
db.write8Byte(addr);
db.write8Byte(size);
db.write4Byte(offset);
db.write4Byte(align);
db.write4Byte(reloff);
db.write4Byte(nreloc);
db.write4Byte(flags);
db.write4Byte(reserved1);
db.write4Byte(reserved2);
db.align(8);
}
@Override
public String toString() {
return String.format("Section Info, name %s, segment %s", sectName, segName) +
String.format("%n address %#x, size %d (%2$#x), offset %d (%3$#x), align %#x", addr, size, offset, align) +
String.format("%n first relocation entry at %d (%1$#x), number of relocation entries %d", reloff, nreloc) +
String.format("%n flags %#x, reserved %d %d", flags, reserved1, reserved2);
}
}
/** See the note about 'effectiveFileSize' in {@link Segment64Command#writePayload}. */
private int minimumFileSize = 0;
@Override
protected int getMinimumFileSize() {
return minimumFileSize;
}
@Override
public int bake(List sortedObjectFileElements) {
minimumFileSize = 0; // re-zero it for this write-out
return super.bake(sortedObjectFileElements);
}
int segmentVaddrGivenFirstSectionVaddr(int sectionVaddr) {
/*
* We round down the minVaddr to the next lower page boundary. And the same for the file
* offset, i.e. some of the previous segment or header/loadcmds gets included in the segment
* image.
*/
int effectiveMinVaddr = ((sectionVaddr >> getPageSizeShift()) << getPageSizeShift());
assert effectiveMinVaddr <= sectionVaddr;
return effectiveMinVaddr;
}
public class Segment64Command extends LoadCommand implements Segment {
String segname; // 16 characters
// long vmaddr; // starting virtual memory address
// long vmsize; // number of bytes of virtual memory occupied by this segment
// long fileoff; // offset in the file at which this segment starts (the first section!)
// long filesize; // number of bytes occupied by this segment on disk
EnumSet maxprot = EnumSet.noneOf(VMProt.class); // maximum permitted virtual memory
// protections of this segment
EnumSet initprot = EnumSet.noneOf(VMProt.class); // initial virtual memory
// protections of this segment
// int nsects; // number of sections
int flags; // flags
@Override
public String getName() {
return segname;
}
@Override
public void setName(String name) {
this.segname = name;
}
List elementsInSegment = new ArrayList<>();
@Override
public boolean isExecutable() {
return initprot.contains(VMProt.EXECUTE);
}
@Override
public boolean isWritable() {
return initprot.contains(VMProt.WRITE);
}
List readStructs = new ArrayList<>();
public Segment64Command(String name, String segmentName) {
super(name, LoadCommandKind.SEGMENT_64);
// creates a new empty segment
this.segname = segmentName;
}
@Override
protected void writePayload(OutputAssembler db, final Map alreadyDecided) {
db.writeStringPadded(segname, 16);
// our virtual address is the lowest of any virtual address issued to
// our constituent loadable sections.
Map decidedAboutOurElements = new HashMap<>();
for (Element e : elementsInSegment) {
if (e instanceof MachOSection) {
decidedAboutOurElements.put(e, alreadyDecided.get(e));
}
}
List minVaddrDecisions = ObjectFile.minimalDecisionValues(decidedAboutOurElements, LayoutDecision.Kind.VADDR, new IntegerDecisionComparator(true));
int minVaddr = (minVaddrDecisions == null || minVaddrDecisions.size() == 0) ? 0 : (int) minVaddrDecisions.get(0).getValue();
// vmsize is the difference between our min vaddr and max vaddr size + that section's
// size rounded up to page size
List maxVaddrDecisions = ObjectFile.maximalDecisionValues(decidedAboutOurElements, LayoutDecision.Kind.VADDR, new IntegerDecisionComparator(false));
// break ties using size
Collections.sort(maxVaddrDecisions, new SizeTiebreakComparator(decidedAboutOurElements, false));
// we sorted into ascending size order, so get the biggest
LayoutDecision maxVaddrDecision = maxVaddrDecisions.get(maxVaddrDecisions.size() - 1);
int maxVaddr = (maxVaddrDecision == null) ? 0 : ((int) maxVaddrDecision.getValue() + maxVaddrDecision.getElement().getMemSize(alreadyDecided));
int vmSize = ObjectFile.nextIntegerMultiple(maxVaddr - minVaddr, getPageSize());
@SuppressWarnings("unused")
Element firstSectionByVaddr = (minVaddrDecisions == null) ? null : minVaddrDecisions.get(0).getElement();
@SuppressWarnings("unused")
Element lastSectionByVaddr = (maxVaddrDecision == null) ? null : maxVaddrDecision.getElement();
// same job for file offsets -- not all elements have vaddrs!
// NOTE: the vaddr case is redundant, but is a useful sanity check
List minOffsetDecisions = ObjectFile.minimalDecisionValues(decidedAboutOurElements, LayoutDecision.Kind.OFFSET, new IntegerDecisionComparator(true));
int minOffset = (minOffsetDecisions == null || minOffsetDecisions.size() == 0) ? 0 : (int) minOffsetDecisions.get(0).getValue();
List maxOffsetDecisions = ObjectFile.maximalDecisionValues(decidedAboutOurElements, LayoutDecision.Kind.OFFSET, new IntegerDecisionComparator(false));
// break ties using size
Collections.sort(maxOffsetDecisions, new SizeTiebreakComparator(decidedAboutOurElements, false));
// we sorted into ascending size order, so get the biggest
LayoutDecision maxOffsetDecision = maxOffsetDecisions.get(maxOffsetDecisions.size() - 1);
Element firstElementByOffset = (minOffsetDecisions == null) ? null : minOffsetDecisions.get(0).getElement();
@SuppressWarnings("unused")
Element lastElementByOffset = (maxOffsetDecision == null) ? null : maxOffsetDecision.getElement();
// these are *not* true because some elements need not have vaddr
// assert firstElementByOffset == firstSectionByVaddr;
// assert lastElementByOffset == lastSectionByVaddr;
// -- FIXME: find out a sensible assertion that should be true along these lines
int fileOffset = (firstElementByOffset == null) ? 0 : (int) alreadyDecided.get(firstElementByOffset).getDecidedValue(LayoutDecision.Kind.OFFSET);
int maxOffset = (maxOffsetDecision == null) ? 0 : ((int) maxOffsetDecision.getValue() + (int) alreadyDecided.get(maxOffsetDecision.getElement()).getDecidedValue(LayoutDecision.Kind.SIZE));
int fileSize = maxOffset - minOffset;
int effectiveMinVaddr = segmentVaddrGivenFirstSectionVaddr(minVaddr);
assert effectiveMinVaddr >= 0;
/* If this is wrong, it means that 4KB was not enough padding in initialVaddr(). */
int prePadding = minVaddr - effectiveMinVaddr;
int effectiveVmSize = vmSize + prePadding;
int effectiveFileOffset = fileOffset - prePadding;
int effectiveFileSize = fileSize + prePadding;
db.write8Byte(effectiveMinVaddr);
db.write8Byte(effectiveVmSize);
db.write8Byte(effectiveFileOffset);
/*
* Round up effectiveFileSize to the nearest page boundary, to match what the stock
* tools do. BUT: ARGH:
*
* 1. the loader complains if this extends beyond end-of-file
*
* 2. the tools don't do this for the __LINKEDIT segment
*
* Approximate this by
*
* - skipping the round-up for the link edit segment
*
* - when we do round up, record a "minimum file size"...
*
* - ... and zero-pad the file to this length.
*/
if (this != getLinkEditSegment()) {
effectiveFileSize = ObjectFile.nextIntegerMultiple(effectiveFileSize, getPageSize());
minimumFileSize = Math.max(minimumFileSize, effectiveFileOffset + effectiveFileSize);
}
db.write8Byte(effectiveFileSize);
db.write4Byte((int) ObjectFile.flagSetAsLong(maxprot));
db.write4Byte((int) ObjectFile.flagSetAsLong(initprot));
int sectionCountPos = db.pos();
db.write4Byte(0); // placeholder for section count
db.write4Byte(flags); // *segment* flags
db.align(8);
int sectionCount = 0;
for (Element el : elementsInSegment) {
// non-Section elements don't get an info struct!
if (!(el instanceof Section)) {
continue;
}
++sectionCount;
MachOSection s = (MachOSection) el;
int logAlignment = (int) (Math.log10(s.getAlignment()) / Math.log10(2.0));
/*
* Find the LinkEditElement, if any, that contains our relocation records. We only
* do this is we're a relocatable file. Dynamic relocs are indexed differently, from
* fields in the LC_DYSYMTAB load command.
*/
MachORelocationElement ourRelocs = null;
if (getLinkEditSegment() != null) {
for (Element e : getLinkEditSegment().elementsInSegment) {
if (e instanceof MachORelocationElement && ((MachORelocationElement) e).relocatesSegment(this)) {
if (ourRelocs == null) {
ourRelocs = (MachORelocationElement) e;
continue;
}
assert false; // i.e. we should not find *another* RelocationElement
// also containing relevant relocs
}
}
}
/*
* If we're a a relocatable file, we should have a destination segment name. An
* initial value is guessed in the MachOSection constructor.
*/
assert s.destinationSegmentName != null;
SectionInfoStruct si = new SectionInfoStruct(
s.getName(),
s.destinationSegmentName,
s.getElement().isReferenceable() ? (int) alreadyDecided.get(s).getDecidedValue(LayoutDecision.Kind.VADDR) : 0,
(int) alreadyDecided.get(s).getDecidedValue(LayoutDecision.Kind.SIZE),
(int) alreadyDecided.get(s).getDecidedValue(LayoutDecision.Kind.OFFSET),
logAlignment,
ourRelocs == null ? 0 : (int) alreadyDecided.get(ourRelocs).getDecidedValue(LayoutDecision.Kind.OFFSET) + ourRelocs.startIndexFor(s) * ourRelocs.encodedEntrySize(),
ourRelocs == null ? 0 : ourRelocs.countFor(s),
(int) ObjectFile.flagSetAsLong(s.flags) | s.type.getValue(),
/* reserved1 */ 0,
/* reserved2 */ 0);
int startPos = db.pos();
si.write(db);
assert db.pos() - startPos == SectionInfoStruct.DEFAULT_SIZE;
}
// go back and fill in the actual section count
db.pushSeek(sectionCountPos);
db.write4Byte(sectionCount);
db.pop();
}
private int sectionsInSegment() {
int count = 0;
for (Element e : elementsInSegment) {
if (e instanceof Section) {
++count;
}
}
return count;
}
@Override
public int getOrDecideSize(Map alreadyDecided, int sizeHint) {
// FIXME: please....
return 4 + 4 + 16 + 8 + 8 + 8 + 8 + 4 + 4 + 4 + 4 + (sectionsInSegment() * SectionInfoStruct.DEFAULT_SIZE);
}
@Override
public Iterable getDependencies(Map decisions) {
// 'minimal' means that our size does not depend on our bytewise-encoded content
HashSet deps = ObjectFile.minimalDependencies(decisions, this);
// our content depends on the offset and size of every section we contain
LayoutDecision ourContent = decisions.get(this).getDecision(LayoutDecision.Kind.CONTENT);
for (Element s : elementsInSegment) {
deps.add(BuildDependency.createOrGet(ourContent, decisions.get(s).getDecision(LayoutDecision.Kind.SIZE)));
deps.add(BuildDependency.createOrGet(ourContent, decisions.get(s).getDecision(LayoutDecision.Kind.OFFSET)));
// our content also depends on the vaddr of every loadable section
// (because we record the vmsize in the segment header)
if (s.getElement().isReferenceable()) {
deps.add(BuildDependency.createOrGet(ourContent, decisions.get(s).getDecision(LayoutDecision.Kind.VADDR)));
}
}
// if (sections.size() > 0) {
// deps.add(new BuildDependency(ourContent,
// decisions.get(sections.get(sections.size() -
// 1)).getDecision(LayoutProperty.Kind.SIZE)));
// }
/*
* If our name is "__LINKEDIT", we're special: it means we have to come last. The way we
* ensure this is to only create such segments at the *last* position in the segments
* list, and to preserve this positioning as new segments are added. We just assert that
* here. See loadCommands.
*/
if ((getName() != null) && (getName().equals("__LINKEDIT"))) {
assert this == loadCommands.linkEditCommand;
} else {
/*
* We also depend on the offset of any relocation element containing relocation
* records for our content.
*/
if (getLinkEditSegment() != null) {
for (Element e : getLinkEditSegment().elementsInSegment) {
if (e instanceof MachORelocationElement && ((MachORelocationElement) e).relocatesSegment(this)) {
// we depend on its offset
deps.add(BuildDependency.createOrGet(ourContent, decisions.get(e).getDecision(LayoutDecision.Kind.OFFSET)));
}
}
}
}
return deps;
}
@Override
public void add(int arg0, Element arg1) {
elementsInSegment.add(arg0, arg1);
}
@Override
public boolean add(Element arg0) {
return elementsInSegment.add(arg0);
}
@Override
public boolean addAll(Collection extends Element> arg0) {
return elementsInSegment.addAll(arg0);
}
@Override
public boolean addAll(int arg0, Collection extends Element> arg1) {
return elementsInSegment.addAll(arg0, arg1);
}
@Override
public void clear() {
elementsInSegment.clear();
}
@Override
public boolean contains(Object arg0) {
return elementsInSegment.contains(arg0);
}
@Override
public boolean containsAll(Collection> arg0) {
return elementsInSegment.containsAll(arg0);
}
@Override
public Element get(int arg0) {
return elementsInSegment.get(arg0);
}
@Override
public int indexOf(Object arg0) {
return elementsInSegment.indexOf(arg0);
}
@Override
public boolean isEmpty() {
return elementsInSegment.isEmpty();
}
@Override
public Iterator iterator() {
return elementsInSegment.iterator();
}
@Override
public int lastIndexOf(Object arg0) {
return elementsInSegment.lastIndexOf(arg0);
}
@Override
public ListIterator listIterator() {
return elementsInSegment.listIterator();
}
@Override
public ListIterator listIterator(int arg0) {
return elementsInSegment.listIterator(arg0);
}
@Override
public Element remove(int arg0) {
return elementsInSegment.remove(arg0);
}
@Override
public boolean remove(Object arg0) {
return elementsInSegment.remove(arg0);
}
@Override
public boolean removeAll(Collection> arg0) {
return elementsInSegment.removeAll(arg0);
}
@Override
public boolean retainAll(Collection> arg0) {
return elementsInSegment.retainAll(arg0);
}
@Override
public Element set(int arg0, Element arg1) {
return elementsInSegment.set(arg0, arg1);
}
@Override
public int size() {
return elementsInSegment.size();
}
@Override
public List subList(int arg0, int arg1) {
return elementsInSegment.subList(arg0, arg1);
}
@Override
public Object[] toArray() {
return elementsInSegment.toArray();
}
@Override
public T[] toArray(T[] arg0) {
return elementsInSegment.toArray(arg0);
}
}
/**
* We model the link edit segment as a separate class. Largely this is because our list of load
* commands needs to handle the link edit command separately. We get added to the list in
* LoadCommand's constructor, at which point the segment name is not available (it's a field in
* the Segment64Command subclass, which is not initialized at the time of the constructor call).
*
*/
public class LinkEditSegment64Command extends Segment64Command {
private MachOSymtab symtab;
private MachOStrtab strtab;
public LinkEditSegment64Command() {
super("LinkEditSegment", "__LINKEDIT");
initprot = EnumSet.of(VMProt.READ); // always give read permission
// native tools give maximum maxprot
maxprot = EnumSet.of(VMProt.READ, VMProt.WRITE, VMProt.EXECUTE);
}
public MachOSymtab getSymtab() {
return symtab;
}
public MachOStrtab getStrtab() {
return strtab;
}
public MachORelocationElement getRelocations() {
return relocs;
}
}
private EnumSet flags = EnumSet.noneOf(Flag.class);
public Set getFlags() {
return Collections.unmodifiableSet(flags);
}
public void setFlags(EnumSet flags) {
this.flags.clear();
this.flags.addAll(flags);
}
protected LinkEditSegment64Command getOrCreateLinkEditSegment() {
// create a __LINKEDIT segment and appropriate symtabs
if (loadCommands.linkEditCommand != null) {
return loadCommands.linkEditCommand;
} else {
return createLinkEditSegment();
}
}
protected LinkEditSegment64Command createLinkEditSegment() {
return new LinkEditSegment64Command();
}
@Override
protected SymbolTable createSymbolTable() {
/*
* If we're dynamic, create a linkedit segment. Otherwise, our caller should have created a
* single segment; we use that.
*/
assert getSegments().size() == 1;
Segment64Command segment = (Segment64Command) getSegments().iterator().next();
// create the strtab too
MachOStrtab strtab = new MachOStrtab("MachOStrtab", MachOObjectFile.this, segment);
MachOSymtab symtab = new MachOSymtab("MachOSymtab", this, segment, strtab);
assert segment.contains(strtab);
assert segment.contains(symtab);
// create the load command pointing at the symtab
SymtabCommand cmd = new SymtabCommand("MachOSymtabCommand", symtab);
assert cmd.symtab == symtab;
return symtab;
}
@Override
public MachOSymtab getSymbolTable() {
/*
* Mach-O symtabs are not sections and do not have names. We find the __LINKEDIT segment so
* we can sanity-check.
*/
Segment segment = null;
/*
* In a shared library, we should find the symtab in the __LINKEDIT segment. In a
* relocatable file, it should be in the segment named "".
*/
final String segmentName = getUnnamedSegmentName();
Set segs = getSegments();
for (Segment seg : segs) {
if (seg.getName().equals(segmentName)) {
segment = seg;
break;
}
}
// if we can't find the relevant segment, we haven't been constructed correctly
assert segment != null;
/*
* Both dynamic and non-dynamic symtabs have their own load command. In the file, this
* command stores the offset of the raw symtab data (which should be in the __LINKEDIT
* segment). In our representation of this command, we keep a reference to the element, so
* we can grab it this way.
*/
for (LoadCommand cmd : loadCommands) {
if (cmd instanceof SymtabCommand) {
MachOSymtab e = ((SymtabCommand) cmd).symtab;
assert segment.contains(e);
return e;
}
}
return null;
}
abstract class LinkEditElement extends Element {
@Override
public ElementImpl getImpl() {
return this;
}
final Segment64Command segment;
LinkEditElement(String name, Segment64Command containingSegment) {
// If we're the first in the __LINKEDIT segment, we align to page size.
this(name, containingSegment, containingSegment.isEmpty() ? getPageSize() : 1);
}
LinkEditElement(String name, Segment64Command containingSegment, int alignment) {
super(name, alignment);
segment = containingSegment;
containingSegment.add(this);
}
@Override
public boolean isLoadable() {
return true;
}
@Override
public int getOrDecideVaddr(Map alreadyDecided, int vaddrHint) {
// we are loadable!
return ObjectFile.defaultGetOrDecideVaddr(alreadyDecided, this, vaddrHint);
}
@Override
public LayoutDecisionMap getDecisions(LayoutDecisionMap copyingIn) {
return ObjectFile.defaultDecisions(this, copyingIn);
}
@Override
public int getOrDecideOffset(Map alreadyDecided, int offsetHint) {
return ObjectFile.defaultGetOrDecideOffset(alreadyDecided, this, offsetHint);
}
}
@SuppressWarnings("unused")
public void addOpaqueLoadCommand(String name, LoadCommandKind k, final byte[] bs) {
new LoadCommand(name, k) {
@Override
protected void writePayload(OutputAssembler out, Map alreadyDecided) {
out.writeBlob(bs);
}
};
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy