546bf07f7d
TEST=ci Change-Id: I65c315e9efc3cd7b8b47d2c167681986278644da Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/502780 Reviewed-by: Alexander Markov <alexmarkov@google.com> Commit-Queue: Ryan Macnak <rmacnak@google.com>
3663 lines
137 KiB
C++
3663 lines
137 KiB
C++
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
#include "vm/mach_o.h"
|
|
|
|
#if defined(DART_PRECOMPILER)
|
|
|
|
#include <utility>
|
|
|
|
#include "openssl/sha.h"
|
|
#include "platform/mach_o.h"
|
|
#include "platform/unwinding_records.h"
|
|
#include "vm/compiler/runtime_api.h"
|
|
#include "vm/dwarf.h"
|
|
#include "vm/dwarf_so_writer.h"
|
|
#include "vm/flags.h"
|
|
#include "vm/hash_map.h"
|
|
#include "vm/image_snapshot.h"
|
|
#include "vm/os.h"
|
|
#include "vm/unwinding_records.h"
|
|
#include "vm/zone_text_buffer.h"
|
|
|
|
namespace dart {
|
|
|
|
DEFINE_FLAG(bool,
|
|
macho_linker_signature,
|
|
true,
|
|
"Whether to include a ad-hoc linker-signed code signature block");
|
|
|
|
DEFINE_FLAG(charp,
|
|
macho_install_name,
|
|
nullptr,
|
|
"The install name to be used for the dynamic library. "
|
|
"The output filename is used if not provided.");
|
|
|
|
DEFINE_FLAG(bool,
|
|
macho_reduce_padding,
|
|
false,
|
|
"Whether to use a smaller alignment size for segments and the "
|
|
"text/const sections in Mach-O outputs.")
|
|
|
|
#if defined(DART_TARGET_OS_MACOS_IOS)
|
|
static constexpr bool kDefaultEncryptable = true;
|
|
#else
|
|
static constexpr bool kDefaultEncryptable = false;
|
|
#endif
|
|
|
|
DEFINE_FLAG(bool,
|
|
macho_encryptable,
|
|
kDefaultEncryptable,
|
|
"Whether to include an LC_ENCRYPTION_INFO load command "
|
|
"in the snapshot");
|
|
|
|
#if defined(DART_TARGET_OS_MACOS) || defined(DART_TARGET_OS_MACOS_IOS)
|
|
DEFINE_FLAG(charp,
|
|
macho_min_os_version,
|
|
nullptr,
|
|
"The minimum OS version required for MacOS/iOS Mach-O snapshots");
|
|
|
|
DEFINE_FLAG(charp,
|
|
macho_rpath,
|
|
nullptr,
|
|
"Run paths to be added at runtime (comma delimited)");
|
|
#endif
|
|
|
|
static constexpr intptr_t kLinearInitValue = -1;
|
|
|
|
#define DEFINE_LINEAR_FIELD_METHODS(name) \
|
|
intptr_t name() const { \
|
|
ASSERT(name##_ != kLinearInitValue); \
|
|
return name##_; \
|
|
} \
|
|
bool name##_is_set() const { \
|
|
return name##_ != kLinearInitValue; \
|
|
} \
|
|
void set_##name(intptr_t value) { \
|
|
ASSERT(value != kLinearInitValue); \
|
|
ASSERT_EQUAL(name##_, kLinearInitValue); \
|
|
name##_ = value; \
|
|
}
|
|
|
|
#define DEFINE_LINEAR_FIELD(name) intptr_t name##_ = kLinearInitValue;
|
|
|
|
// Only subclasses of MachOContents that need to be distinguished dynamically
|
|
// via Is/As checks are listed here.
|
|
#define FOR_EACH_CHECKABLE_MACHO_CONTENTS_TYPE(V) \
|
|
V(MachOCommand) \
|
|
V(MachOSegment) \
|
|
V(MachOSection) \
|
|
V(MachOHeader)
|
|
|
|
#define DEFINE_TYPE_CHECK_FOR(Type) \
|
|
bool Is##Type() const override { \
|
|
return true; \
|
|
}
|
|
|
|
#if defined(DART_TARGET_OS_MACOS) || defined(DART_TARGET_OS_MACOS_IOS)
|
|
#define FOR_EACH_MACOS_ONLY_CONCRETE_MACHO_CONTENTS_TYPE(V) \
|
|
V(MachORunPath) \
|
|
V(MachOBuildVersion) \
|
|
V(MachOLoadDylib)
|
|
#else
|
|
#define FOR_EACH_MACOS_ONLY_CONCRETE_MACHO_CONTENTS_TYPE(V)
|
|
#endif
|
|
|
|
// All concrete subclasses of MachOContents should go here:
|
|
#define FOR_EACH_CONCRETE_MACHO_CONTENTS_TYPE(V) \
|
|
FOR_EACH_MACOS_ONLY_CONCRETE_MACHO_CONTENTS_TYPE(V) \
|
|
V(MachOHeader) \
|
|
V(MachOSegment) \
|
|
V(MachOSection) \
|
|
V(MachOSymbolTable) \
|
|
V(MachODynamicSymbolTable) \
|
|
V(MachOUuid) \
|
|
V(MachOIdDylib) \
|
|
V(MachOCodeSignature) \
|
|
V(MachOEncryptionInfo)
|
|
|
|
#define DECLARE_CONTENTS_TYPE_CLASS(Type) class Type;
|
|
FOR_EACH_CHECKABLE_MACHO_CONTENTS_TYPE(DECLARE_CONTENTS_TYPE_CLASS)
|
|
FOR_EACH_CONCRETE_MACHO_CONTENTS_TYPE(DECLARE_CONTENTS_TYPE_CLASS)
|
|
#undef DECLARE_CONTENTS_TYPE_CLASS
|
|
|
|
using MachORelocationsArray = ZoneGrowableArray<mach_o::relocation_info>;
|
|
using MachORelocationAddendsArray = ZoneGrowableArray<intptr_t>;
|
|
|
|
// The interface for a SharedObjectWriter::WriteStream with MachO-specific
|
|
// utility methods.
|
|
//
|
|
// If HasHashes() is true, the stream calculates and store hashes of
|
|
// written content up to the point that FinalizeHashedContent() is called.
|
|
class MachOWriteStream : public SharedObjectWriter::WriteStream {
|
|
template <typename T, typename S>
|
|
using only_if_unsigned = typename std::enable_if_t<std::is_unsigned_v<T>, S>;
|
|
using Relocation = SharedObjectWriter::Relocation;
|
|
|
|
public:
|
|
explicit MachOWriteStream(const MachOWriter& macho)
|
|
: SharedObjectWriter::WriteStream(macho.type()), macho_(macho) {}
|
|
|
|
const MachOHeader& Header() const;
|
|
const MachOSegment& TextSegment() const;
|
|
|
|
// Write methods that write values of a certain size out to disk.
|
|
// The disk are written in host endian format, which matches the
|
|
// header's magic value (since it is also written with this).
|
|
void Write16(uword value) { WriteBytes(&value, sizeof(uint16_t)); }
|
|
void Write32(uint32_t value) { WriteBytes(&value, sizeof(uint32_t)); }
|
|
void Write64(uint64_t value) { WriteBytes(&value, sizeof(uint64_t)); }
|
|
void WriteWord(compiler::target::uword value) {
|
|
WriteBytes(&value, sizeof(compiler::target::uword));
|
|
}
|
|
|
|
// Write methods that force big endian output. Used in the code signature.
|
|
void WriteBE16(uint16_t value) { Write16(Utils::HostToBigEndian16(value)); }
|
|
void WriteBE32(uint32_t value) { Write32(Utils::HostToBigEndian32(value)); }
|
|
void WriteBE64(uint64_t value) { Write64(Utils::HostToBigEndian64(value)); }
|
|
|
|
// Many load commands have adjacent uint32_t fields that correspond to an
|
|
// offset into the file and a number of bytes or objects to read starting
|
|
// from that offset, so abstract that out to make such writes stand out.
|
|
void WriteOffsetCount(uintptr_t offset, uintptr_t count) {
|
|
ASSERT(Utils::IsUint(32, offset));
|
|
Write32(offset);
|
|
ASSERT(Utils::IsUint(32, count));
|
|
Write32(count);
|
|
}
|
|
|
|
void WriteNullTerminatedCString(const char* str) {
|
|
WriteBytes(str, strlen(str) + 1);
|
|
}
|
|
|
|
// Writes the first n bytes of the given string. If the string is shorter
|
|
// than n bytes, then the remainder of the space is padded with '\0'.
|
|
void WriteFixedLengthCString(const char* str, intptr_t n) {
|
|
const intptr_t len = strlen(str);
|
|
WriteBytes(str, n - len <= 0 ? n : len);
|
|
for (intptr_t i = n - len; i > 0; --i) {
|
|
WriteByte('\0');
|
|
}
|
|
}
|
|
|
|
bool HasValueForLabel(intptr_t label, intptr_t* value) const override;
|
|
|
|
// The maximum size of a chunk of hashed content.
|
|
static constexpr intptr_t kChunkSize = 1 << 12;
|
|
static_assert(Utils::IsPowerOfTwo(kChunkSize));
|
|
|
|
// Used for cs_code_directory::hash_type.
|
|
static constexpr uint8_t kHashType = mach_o::CS_HASHTYPE_SHA256;
|
|
// used for cs_code_directory::hash_size.
|
|
static constexpr uint8_t kHashSize = SHA256_DIGEST_LENGTH;
|
|
|
|
// Whether or not this MachOWriter supports hashing content.
|
|
virtual bool HasHashes() const = 0;
|
|
// The number of hashes calculated from the hashed content.
|
|
// Assumes the hashed content has already been finalized.
|
|
virtual intptr_t num_hashes() const = 0;
|
|
// Writes the calculated hashes to the stream.
|
|
// Assumes the hashed content has already been finalized.
|
|
virtual void WriteHashes() = 0;
|
|
// Call once all content that should be hashed has been written to the stream.
|
|
virtual void FinalizeHashedContent() = 0;
|
|
|
|
void set_current_relocation_addends(
|
|
const MachORelocationAddendsArray* array) {
|
|
current_relocation_addends_ = array;
|
|
}
|
|
|
|
protected:
|
|
void WriteRelocatableValue(intptr_t address,
|
|
const Relocation& reloc,
|
|
intptr_t reloc_index) override {
|
|
if (type() != SharedObjectWriter::Type::Object) {
|
|
// Use the super implementation.
|
|
return SharedObjectWriter::WriteStream::WriteRelocatableValue(
|
|
address, reloc, reloc_index);
|
|
}
|
|
// Relocatable objects do not resolve relocations eagerly unless
|
|
// the source and target are the same, in which case the eagerly
|
|
// computed value has already been calculated as the "addend".
|
|
intptr_t to_write = 0;
|
|
#if defined(TARGET_ARCH_X64) || defined(TARGET_ARCH_ARM64)
|
|
// For X64 and ARM64, the addend is stored in the relocated location
|
|
// as the MachOWriter only uses UNSIGNED/SUBTRACTOR relocation entries.
|
|
RELEASE_ASSERT(current_relocation_addends_ != nullptr);
|
|
to_write = current_relocation_addends_->At(reloc_index);
|
|
#else
|
|
// Relocatable objects aren't handled for this architecture.
|
|
UNREACHABLE();
|
|
#endif
|
|
ASSERT(Utils::IsInt(reloc.size_in_bytes * kBitsPerByte, to_write));
|
|
WriteBytes(reinterpret_cast<const uint8_t*>(&to_write),
|
|
reloc.size_in_bytes);
|
|
}
|
|
|
|
const MachOWriter& macho_;
|
|
const MachORelocationAddendsArray* current_relocation_addends_ = nullptr;
|
|
|
|
private:
|
|
DISALLOW_COPY_AND_ASSIGN(MachOWriteStream);
|
|
};
|
|
|
|
// A MachOWriteStream that strictly delegates to the provided BaseWriteStream
|
|
// without any internal caching.
|
|
class NonHashingMachOWriteStream
|
|
: public SharedObjectWriter::DelegatingWriteStream,
|
|
public MachOWriteStream {
|
|
public:
|
|
explicit NonHashingMachOWriteStream(BaseWriteStream* stream,
|
|
const MachOWriter& macho)
|
|
: SharedObjectWriter::DelegatingWriteStream(stream, macho),
|
|
MachOWriteStream(macho) {}
|
|
|
|
intptr_t Position() const override {
|
|
return SharedObjectWriter::DelegatingWriteStream::Position();
|
|
}
|
|
void WriteByte(const uint8_t value) override {
|
|
SharedObjectWriter::DelegatingWriteStream::WriteByte(value);
|
|
}
|
|
void WriteBytes(const void* bytes, intptr_t len) override {
|
|
SharedObjectWriter::DelegatingWriteStream::WriteBytes(bytes, len);
|
|
}
|
|
intptr_t Align(intptr_t alignment, intptr_t offset = 0) override {
|
|
return SharedObjectWriter::DelegatingWriteStream::Align(alignment, offset);
|
|
}
|
|
bool HasValueForLabel(intptr_t label, intptr_t* value) const override {
|
|
return MachOWriteStream::HasValueForLabel(label, value);
|
|
}
|
|
|
|
bool HasHashes() const override { return false; }
|
|
intptr_t num_hashes() const override { UNREACHABLE(); }
|
|
void WriteHashes() override { UNREACHABLE(); }
|
|
void FinalizeHashedContent() override { UNREACHABLE(); }
|
|
|
|
private:
|
|
DISALLOW_COPY_AND_ASSIGN(NonHashingMachOWriteStream);
|
|
};
|
|
|
|
// A wrapper around an BaseWriteStream that calculates hashes for kChunkSize
|
|
// chunks being flushed.
|
|
//
|
|
// FinalizeHashedContent() is called after the last write of content that
|
|
// should be hashed; further writes skip the hashing process.
|
|
// (E.g., FinalizeHashes() is called before writing the code signature in
|
|
// a Mach-O file.)
|
|
class HashingMachOWriteStream : public BaseWriteStream,
|
|
public MachOWriteStream {
|
|
public:
|
|
HashingMachOWriteStream(Zone* zone,
|
|
BaseWriteStream* stream,
|
|
const MachOWriter& macho)
|
|
: BaseWriteStream(stream->initial_size()),
|
|
MachOWriteStream(macho),
|
|
zone_(zone),
|
|
wrapped_stream_(stream),
|
|
hashes_(zone, SHA256_DIGEST_LENGTH) {
|
|
// So that we can use the underlying stream's Align, as all alignments
|
|
// will be less than or equal to this alignment.
|
|
ASSERT(Utils::IsAligned(stream->Position(), macho_.page_size()));
|
|
}
|
|
|
|
~HashingMachOWriteStream() {
|
|
// Hashed content should always been finalized earlier so the
|
|
// hashes can be retrieved before destruction.
|
|
ASSERT(!hashing_);
|
|
Flush(/*chunks_only=*/false); // Flush all bytes.
|
|
ASSERT_EQUAL(BaseWriteStream::Position(), 0);
|
|
}
|
|
|
|
intptr_t Position() const override {
|
|
return flushed_size_ + BaseWriteStream::Position();
|
|
}
|
|
void WriteByte(const uint8_t value) override {
|
|
BaseWriteStream::WriteByte(value);
|
|
}
|
|
void WriteBytes(const void* bytes, intptr_t len) override {
|
|
BaseWriteStream::WriteBytes(bytes, len);
|
|
}
|
|
intptr_t Align(intptr_t alignment, intptr_t offset = 0) override {
|
|
ASSERT(Utils::IsPowerOfTwo(alignment));
|
|
ASSERT(alignment <= macho_.page_size());
|
|
return BaseWriteStream::Align(alignment, offset);
|
|
}
|
|
|
|
bool HasHashes() const override { return true; }
|
|
intptr_t num_hashes() const override {
|
|
ASSERT(!hashing_); // Don't allow uses until hashes are finalized.
|
|
return num_hashes_;
|
|
}
|
|
void WriteHashes() override {
|
|
ASSERT(!hashing_); // Don't allow uses until hashes are finalized.
|
|
WriteBytes(hashes_.buffer(), num_hashes_ * kHashSize);
|
|
}
|
|
|
|
// First hashes and then flushes all data in the internal buffer. Afterwards,
|
|
// the internal buffer is empty and future Flush() calls no longer perform
|
|
// hashing before flushing to the wrapped stream.
|
|
//
|
|
// Changes current_ and flushed_size_ accordingly.
|
|
void FinalizeHashedContent() override {
|
|
Flush(/*chunks_only=*/false);
|
|
hashing_ = false; // End of the hashed content.
|
|
// The only content in the hashes buffer should be the hashes themselves.
|
|
ASSERT_EQUAL(num_hashes_ * kHashSize, hashes_.Position());
|
|
}
|
|
|
|
private:
|
|
// Hashes [count] bytes of [buffer_] in [kChunkSize]-sized chunks and
|
|
// returns the number of bytes hashed.
|
|
intptr_t Hash(intptr_t count) {
|
|
ASSERT(count >= 0);
|
|
if (count > 0) {
|
|
ASSERT(count <= BaseWriteStream::Position());
|
|
for (intptr_t offset = 0; offset < count; offset += kChunkSize) {
|
|
const intptr_t len = Utils::Minimum(count - offset, kChunkSize);
|
|
SHA256(buffer_ + offset, len, digest_);
|
|
hashes_.WriteBytes(digest_, kHashSize);
|
|
num_hashes_ += 1;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
// If hashing, then hash all complete chunks and, if [chunks_only] is false,
|
|
// a final incomplete one, then flush all hashed bytes to the wrapped stream.
|
|
// The internal buffer is then reset to contain only unhashed bytes (if any).
|
|
//
|
|
// If not hashing, then all cached content is flushed immediately.
|
|
//
|
|
// Changes current_ and flushed_size_ accordingly.
|
|
void Flush(bool chunks_only) {
|
|
intptr_t size_to_flush = BaseWriteStream::Position();
|
|
if (hashing_) {
|
|
intptr_t size_to_hash = size_to_flush;
|
|
if (chunks_only) {
|
|
size_to_hash -= size_to_hash % kChunkSize;
|
|
}
|
|
size_to_flush = Hash(size_to_hash);
|
|
}
|
|
FlushBytes(size_to_flush);
|
|
}
|
|
|
|
// Flushes the initial [count] bytes of [buffer_] to the wrapped stream.
|
|
//
|
|
// Changes current_ and flushed_size_ accordingly.
|
|
void FlushBytes(intptr_t count) {
|
|
ASSERT(count >= 0);
|
|
if (count == 0) return;
|
|
const intptr_t remaining = BaseWriteStream::Position() - count;
|
|
ASSERT(remaining >= 0);
|
|
wrapped_stream_->WriteBytes(buffer_, count);
|
|
flushed_size_ += count;
|
|
if (remaining > 0) {
|
|
memmove(buffer_, buffer_ + count, remaining);
|
|
}
|
|
current_ = buffer_ + remaining;
|
|
}
|
|
|
|
void Realloc(intptr_t new_size) override {
|
|
Flush(/*chunks_only=*/true);
|
|
// Check whether there's enough space after flushing.
|
|
if (new_size <= Remaining()) return;
|
|
// There isn't, so realloc the buffer.
|
|
const intptr_t old_offset = BaseWriteStream::Position();
|
|
buffer_ = zone_->Realloc(buffer_, capacity_, new_size);
|
|
capacity_ = buffer_ != nullptr ? new_size : 0;
|
|
current_ = buffer_ != nullptr ? buffer_ + old_offset : nullptr;
|
|
}
|
|
|
|
void SetPosition(intptr_t value) override {
|
|
// Make sure we're not trying to set the position to already-flushed data.
|
|
ASSERT(value >= flushed_size_);
|
|
BaseWriteStream::SetPosition(value - flushed_size_);
|
|
}
|
|
|
|
Zone* const zone_;
|
|
BaseWriteStream* const wrapped_stream_;
|
|
ZoneWriteStream hashes_;
|
|
bool hashing_ = true;
|
|
intptr_t flushed_size_ = 0;
|
|
intptr_t num_hashes_ = 0;
|
|
uint8_t digest_[kHashSize]; // Used for SHA256().
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(HashingMachOWriteStream);
|
|
};
|
|
|
|
// A superclass for all objects that represent some content in the MachO output.
|
|
class MachOContents : public ZoneObject {
|
|
public:
|
|
explicit MachOContents(bool needs_offset = true, bool in_segment = true)
|
|
// Set the file offset and/or (relative) memory address to 0 if unneeded.
|
|
: file_offset_(needs_offset ? kLinearInitValue : 0),
|
|
memory_address_(in_segment ? kLinearInitValue : 0) {}
|
|
virtual ~MachOContents() {}
|
|
|
|
struct Visitor : public ValueObject {
|
|
public:
|
|
Visitor() {}
|
|
virtual ~Visitor() {}
|
|
|
|
virtual void Default(MachOContents* c) {}
|
|
|
|
#define DEFINE_VISIT_METHOD(Type) \
|
|
virtual void Visit##Type(Type* m) { \
|
|
Default(reinterpret_cast<MachOContents*>(m)); \
|
|
}
|
|
FOR_EACH_CONCRETE_MACHO_CONTENTS_TYPE(DEFINE_VISIT_METHOD)
|
|
#undef DEFINE_VISIT_METHOD
|
|
|
|
private:
|
|
DISALLOW_COPY_AND_ASSIGN(Visitor);
|
|
};
|
|
|
|
virtual void Accept(Visitor* visitor) = 0;
|
|
virtual void VisitChildren(Visitor* visitor) {}
|
|
|
|
// Content methods.
|
|
|
|
// Whether WriteSelf() for this object or any nested object writes content
|
|
// to the file. For most objects, the file offset is set to 0 at construction
|
|
// if no content is written by it or nested objects.
|
|
//
|
|
// Overwrite this if the computed file offset can be 0 (e.g., the header).
|
|
virtual bool HasContents() const { return file_offset_ != 0; }
|
|
|
|
// Returns the size written to disk by WriteSelf().
|
|
//
|
|
// Only needs to be overwritten for unallocated objects or objects where
|
|
// the number of bytes written by WriteSelf() does not match SelfMemorySize().
|
|
virtual intptr_t SelfFileSize() const {
|
|
if (!HasContents()) return 0;
|
|
return SelfMemorySize();
|
|
}
|
|
|
|
// Writes the file contents for this object to the stream.
|
|
//
|
|
// Note that this does not write the load command for a command, as that
|
|
// is handled separately by MachOCommand::WriteLoadCommand().
|
|
//
|
|
// Only needs to be overwritten for objects with non-zero SelfFileSize().
|
|
virtual void WriteSelf(MachOWriteStream* stream) const {
|
|
ASSERT_EQUAL(SelfFileSize(), 0);
|
|
return;
|
|
}
|
|
|
|
// Returns whether the contents of an object is a segment or contained within
|
|
// a segment and thus has an assigned relative memory address. If it has none,
|
|
// then the memory offset is set to 0 at construction.
|
|
//
|
|
// Note: While technically load commands are in a segment due to being in the
|
|
// header, this returns false for commands that only generate load commands.
|
|
//
|
|
// Should be overwritten if a segment or segment-contained object has a
|
|
// computed relative memory address of 0 (e.g., the header).
|
|
virtual bool IsAllocated() const { return memory_address_ != 0; }
|
|
|
|
// Returns the size allocated in the output's memory space for this object
|
|
// without including any allocation for nested objects.
|
|
//
|
|
// Should be overridden for allocated objects.
|
|
virtual intptr_t SelfMemorySize() const {
|
|
if (!IsAllocated()) return 0;
|
|
UNREACHABLE();
|
|
}
|
|
|
|
// Utility/miscellaneous methods.
|
|
|
|
#define DEFINE_BASE_TYPE_CHECKS(Type) \
|
|
Type* As##Type() { \
|
|
return Is##Type() ? reinterpret_cast<Type*>(this) : nullptr; \
|
|
} \
|
|
const Type* As##Type() const { \
|
|
return const_cast<Type*>(const_cast<MachOContents*>(this)->As##Type()); \
|
|
} \
|
|
virtual bool Is##Type() const { return false; }
|
|
|
|
FOR_EACH_CHECKABLE_MACHO_CONTENTS_TYPE(DEFINE_BASE_TYPE_CHECKS)
|
|
#undef DEFINE_BASE_TYPE_CHECKS
|
|
|
|
// Returns the alignment needed for the non-header contents.
|
|
virtual intptr_t Alignment() const {
|
|
// No need to override for non-allocated commands with no contents.
|
|
ASSERT(!IsAllocated() && !HasContents());
|
|
UNREACHABLE();
|
|
}
|
|
|
|
// The size of the contents written to disk by WriteSelf() for this
|
|
// object and any nested subobjects.
|
|
//
|
|
// Should be overwritten for objects that can have different
|
|
// file and memory sizes.
|
|
virtual intptr_t FileSize() const {
|
|
if (!HasContents()) return 0;
|
|
ASSERT(IsAllocated());
|
|
return MemorySize();
|
|
}
|
|
|
|
// The size of this object and any subobjects combined in the output's memory
|
|
// space. Note that objects may have a different MemorySize() than FileSize()
|
|
// (e.g., a segment that contains zerofill sections).
|
|
//
|
|
// Should be overridden when the object contains nested objects.
|
|
virtual intptr_t MemorySize() const { return SelfMemorySize(); }
|
|
|
|
#define FOR_EACH_CONTENTS_LINEAR_FIELD(M) \
|
|
M(file_offset) \
|
|
M(memory_address)
|
|
|
|
FOR_EACH_CONTENTS_LINEAR_FIELD(DEFINE_LINEAR_FIELD_METHODS);
|
|
|
|
protected:
|
|
static bool HasEncryptionInfo(SharedObjectWriter::Type type) {
|
|
// Return the same result for debug info as for snapshots so that
|
|
// padding calculations for it mimic those for snapshots and so
|
|
// keeps offsets/addresses the same.
|
|
return FLAG_macho_encryptable &&
|
|
(type == SharedObjectWriter::Type::Snapshot ||
|
|
type == SharedObjectWriter::Type::DebugInfo);
|
|
}
|
|
|
|
private:
|
|
FOR_EACH_CONTENTS_LINEAR_FIELD(DEFINE_LINEAR_FIELD);
|
|
|
|
#undef FOR_EACH_CONTENTS_LINEAR_FIELD
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(MachOContents);
|
|
};
|
|
|
|
// Each MachO command corresponds to two parts in the file contents:
|
|
// the load command in the header that describes how to load the command
|
|
// contents and the command contents somewhere after the header.
|
|
//
|
|
// The load command is written via WriteLoadCommand() while WriteSelf()
|
|
// handles writing the command contents.
|
|
//
|
|
// Each concrete subclass of MachOCommand should define
|
|
// static constexpr uint32_t kCommandCode = ...
|
|
// with the appropriate mach_o::LC_* constant.
|
|
class MachOCommand : public MachOContents {
|
|
public:
|
|
explicit MachOCommand(intptr_t cmd,
|
|
bool needs_offset = true,
|
|
bool in_segment = true)
|
|
: MachOContents(needs_offset, in_segment), cmd_(cmd) {
|
|
ASSERT(Utils::IsUint(32, cmd));
|
|
}
|
|
|
|
DEFINE_TYPE_CHECK_FOR(MachOCommand)
|
|
|
|
// Load command fields and methods.
|
|
|
|
// The value identifying the type of section the load command represents.
|
|
// Should be one of the LC_* constants in platform/mach_o.h.
|
|
uint32_t cmd() const { return cmd_; }
|
|
|
|
// The alignment expected for load commands.
|
|
static constexpr intptr_t kLoadCommandAlignment = compiler::target::kWordSize;
|
|
|
|
// The size of the load command representing this command in the header.
|
|
//
|
|
// Note that all load commands must have a size that is a multiple of
|
|
// kLoadCommandAlignment, so padding may be required.
|
|
virtual uint32_t cmdsize() const = 0;
|
|
|
|
// Each load command has a common prefix, which is written by the
|
|
// class's WriteLoadCommand. Call the base class's implementation
|
|
// prior to writing the rest of the load command for the subclass.
|
|
virtual void WriteLoadCommand(MachOWriteStream* stream) const {
|
|
stream->Write32(cmd());
|
|
stream->Write32(cmdsize());
|
|
}
|
|
|
|
// Only the offset within the header is defined since the file offset
|
|
// and memory address for the load command can be derived from the
|
|
// header's file offset and memory address using this offset.
|
|
#define FOR_EACH_COMMAND_LINEAR_FIELD(M) M(header_offset)
|
|
|
|
FOR_EACH_COMMAND_LINEAR_FIELD(DEFINE_LINEAR_FIELD_METHODS);
|
|
|
|
private:
|
|
FOR_EACH_COMMAND_LINEAR_FIELD(DEFINE_LINEAR_FIELD);
|
|
|
|
#undef FOR_EACH_COMMAND_LINEAR_FIELD
|
|
|
|
private:
|
|
uint32_t cmd_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(MachOCommand);
|
|
};
|
|
|
|
class MachOSection : public MachOContents {
|
|
#if defined(TARGET_ARCH_IS_32_BIT)
|
|
using SectionType = mach_o::section;
|
|
#else
|
|
using SectionType = mach_o::section_64;
|
|
#endif
|
|
|
|
public:
|
|
MachOSection(Zone* zone,
|
|
const char* name,
|
|
const char* segname,
|
|
intptr_t alignment,
|
|
intptr_t type = mach_o::S_REGULAR,
|
|
intptr_t attributes = mach_o::S_NO_ATTRIBUTES,
|
|
bool has_contents = true)
|
|
: MachOContents(/*needs_offset=*/has_contents,
|
|
/*in_segment=*/true),
|
|
name_(name),
|
|
segname_(segname),
|
|
flags_(mach_o::SectionFlags(type, attributes)),
|
|
alignment_(alignment),
|
|
portions_(zone, 0) {
|
|
ASSERT(strlen(name) <= sizeof(SectionType::sectname));
|
|
ASSERT(strlen(segname) <= sizeof(SectionType::segname));
|
|
ASSERT(Utils::IsPowerOfTwo(alignment));
|
|
ASSERT_EQUAL(type & mach_o::SECTION_TYPE, static_cast<uint32_t>(type));
|
|
ASSERT_EQUAL(attributes & mach_o::SECTION_ATTRIBUTES,
|
|
static_cast<uint32_t>(attributes));
|
|
if (type == mach_o::S_ZEROFILL && type == mach_o::S_GB_ZEROFILL) {
|
|
ASSERT(!has_contents);
|
|
}
|
|
}
|
|
|
|
DEFINE_TYPE_CHECK_FOR(MachOSection)
|
|
|
|
intptr_t Alignment() const override { return alignment_; }
|
|
|
|
const char* name() const { return name_; }
|
|
const char* segname() const { return segname_; }
|
|
|
|
bool HasName(const char* name) const { return strcmp(name_, name) == 0; }
|
|
bool HasSegname(const char* segname) const {
|
|
return strcmp(segname_, segname) == 0;
|
|
}
|
|
|
|
intptr_t index() const {
|
|
// The getter should not be called until after an initial index is assigned.
|
|
ASSERT(index_ != mach_o::NO_SECT);
|
|
return index_;
|
|
}
|
|
void set_index(intptr_t value) {
|
|
ASSERT(value != mach_o::NO_SECT);
|
|
index_ = value;
|
|
}
|
|
|
|
struct Portion {
|
|
void Write(MachOWriteStream* stream, intptr_t section_start) const {
|
|
ASSERT(bytes != nullptr);
|
|
if (relocations != nullptr) {
|
|
const intptr_t address = section_start + offset;
|
|
stream->WriteBytesWithRelocations(bytes, size, address, *relocations);
|
|
} else {
|
|
stream->WriteBytes(bytes, size);
|
|
}
|
|
}
|
|
|
|
bool ContainsSymbols() const {
|
|
return symbol_name != nullptr ||
|
|
(symbols != nullptr && !symbols->is_empty());
|
|
}
|
|
|
|
intptr_t offset;
|
|
const char* symbol_name;
|
|
intptr_t label;
|
|
const uint8_t* bytes;
|
|
intptr_t size;
|
|
const SharedObjectWriter::RelocationArray* relocations;
|
|
const SharedObjectWriter::SymbolDataArray* symbols;
|
|
|
|
private:
|
|
DISALLOW_ALLOCATION();
|
|
};
|
|
|
|
const GrowableArray<Portion>& portions() const { return portions_; }
|
|
|
|
void AddPortion(
|
|
const uint8_t* bytes,
|
|
intptr_t size,
|
|
const SharedObjectWriter::RelocationArray* relocations = nullptr,
|
|
const SharedObjectWriter::SymbolDataArray* symbols = nullptr,
|
|
const char* symbol_name = nullptr,
|
|
intptr_t label = 0) {
|
|
// Any named portion should also have a valid symbol label.
|
|
ASSERT(symbol_name == nullptr || label > 0);
|
|
ASSERT(!HasContents() || bytes != nullptr);
|
|
ASSERT(bytes != nullptr || relocations == nullptr);
|
|
// Make sure all portions are consistent in containing bytes.
|
|
ASSERT(portions_.is_empty() ||
|
|
(portions_[0].bytes != nullptr) == (bytes != nullptr));
|
|
intptr_t offset = 0;
|
|
if (!portions_.is_empty()) {
|
|
const auto& last = portions_.Last();
|
|
offset = last.offset + last.size;
|
|
}
|
|
// Each portion is aligned within the section.
|
|
offset = Utils::RoundUp(offset, Alignment());
|
|
portions_.Add(
|
|
{offset, symbol_name, label, bytes, size, relocations, symbols});
|
|
}
|
|
|
|
intptr_t SelfMemorySize() const override {
|
|
const auto& last = portions_.Last();
|
|
return last.offset + last.size;
|
|
}
|
|
|
|
// The first section in relocated objects will have a memory offset of 0, so
|
|
// don't use the superclass's implementation as all sections are allocated.
|
|
bool IsAllocated() const override { return true; }
|
|
|
|
void WriteSelf(MachOWriteStream* stream) const override {
|
|
if (!HasContents()) return;
|
|
stream->set_current_relocation_addends(relocation_addends_);
|
|
for (const auto& portion : portions_) {
|
|
// Each portion is aligned within the section.
|
|
stream->Align(Alignment());
|
|
ASSERT_EQUAL(stream->Position(), file_offset() + portion.offset);
|
|
portion.Write(stream, memory_address());
|
|
}
|
|
stream->set_current_relocation_addends(nullptr);
|
|
}
|
|
|
|
const Portion* FindPortion(const char* symbol_name) const {
|
|
for (const auto& portion : portions_) {
|
|
if (strcmp(symbol_name, portion.symbol_name) == 0) {
|
|
return &portion;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
bool ContainsSymbols() const {
|
|
for (const auto& p : portions_) {
|
|
if (p.ContainsSymbols()) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Accept(Visitor* visitor) override { visitor->VisitMachOSection(this); }
|
|
|
|
const MachORelocationsArray* relocations() const { return relocations_; }
|
|
void set_relocations(const MachORelocationsArray* relocations) {
|
|
relocations_ = relocations;
|
|
}
|
|
intptr_t num_relocations() const {
|
|
return relocations_ == nullptr ? 0 : relocations_->length();
|
|
}
|
|
|
|
const MachORelocationAddendsArray* relocation_addends() const {
|
|
return relocation_addends_;
|
|
}
|
|
void set_relocation_addends(const MachORelocationAddendsArray* array) {
|
|
relocation_addends_ = array;
|
|
}
|
|
|
|
private:
|
|
uint32_t HeaderInfoSize() const { return sizeof(SectionType); }
|
|
|
|
// Called during MachOSegment::WriteLoadCommand.
|
|
void WriteHeaderInfo(MachOWriteStream* stream) const {
|
|
auto const start = stream->Position();
|
|
stream->WriteFixedLengthCString(name_, sizeof(SectionType::sectname));
|
|
stream->WriteFixedLengthCString(segname_, sizeof(SectionType::segname));
|
|
// While
|
|
stream->WriteWord(memory_address());
|
|
stream->WriteWord(MemorySize());
|
|
stream->Write32(file_offset());
|
|
stream->Write32(Utils::ShiftForPowerOfTwo(Alignment()));
|
|
stream->WriteOffsetCount(relocations_file_offset(), num_relocations());
|
|
stream->Write32(flags_);
|
|
// All reserved fields are 0 for our purposes.
|
|
stream->Write32(0); // reserved1
|
|
stream->Write32(0); // reserved2
|
|
#if defined(TARGET_ARCH_IS_64_BIT)
|
|
stream->Write32(0); // reserved3
|
|
#endif
|
|
ASSERT_EQUAL(stream->Position(),
|
|
static_cast<intptr_t>(start + HeaderInfoSize()));
|
|
}
|
|
|
|
const char* const name_;
|
|
const char* const segname_;
|
|
const decltype(SectionType::flags) flags_ = 0;
|
|
const intptr_t alignment_;
|
|
intptr_t index_ = mach_o::NO_SECT;
|
|
GrowableArray<Portion> portions_;
|
|
// The array of relocation_info structs that should be output for this
|
|
// section iff the output format is a relocatable object.
|
|
const MachORelocationsArray* relocations_ = nullptr;
|
|
// A list of relocation addends for relocatable objects.
|
|
const MachORelocationAddendsArray* relocation_addends_ = nullptr;
|
|
|
|
#define FOR_EACH_CONTENTS_LINEAR_FIELD(M) M(relocations_file_offset)
|
|
|
|
public:
|
|
FOR_EACH_CONTENTS_LINEAR_FIELD(DEFINE_LINEAR_FIELD_METHODS);
|
|
|
|
private:
|
|
FOR_EACH_CONTENTS_LINEAR_FIELD(DEFINE_LINEAR_FIELD);
|
|
|
|
#undef FOR_EACH_CONTENTS_LINEAR_FIELD
|
|
|
|
friend class MachOSegment;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(MachOSection);
|
|
};
|
|
|
|
class MachOSegment : public MachOCommand {
|
|
#if defined(TARGET_ARCH_IS_32_BIT)
|
|
using SegmentCommandType = mach_o::segment_command;
|
|
#else
|
|
using SegmentCommandType = mach_o::segment_command_64;
|
|
#endif
|
|
|
|
using SnapshotType = SharedObjectWriter::Type;
|
|
|
|
public:
|
|
#if defined(TARGET_ARCH_IS_32_BIT)
|
|
static constexpr uint32_t kCommandCode = mach_o::LC_SEGMENT;
|
|
#else
|
|
static constexpr uint32_t kCommandCode = mach_o::LC_SEGMENT_64;
|
|
#endif
|
|
|
|
MachOSegment(Zone* zone,
|
|
SnapshotType type,
|
|
const char* name,
|
|
intptr_t initial_vm_protection = mach_o::VM_PROT_READ,
|
|
intptr_t max_vm_protection = mach_o::VM_PROT_READ)
|
|
// We don't know if a segment has a file offset until we
|
|
// know what it contains, so set it to 0 in ComputeOffsets()
|
|
// if there are no contents.
|
|
: MachOCommand(kCommandCode),
|
|
type_(type),
|
|
name_(name),
|
|
initial_vm_protection_(initial_vm_protection),
|
|
max_vm_protection_(max_vm_protection),
|
|
contents_(zone, 0) {
|
|
ASSERT(Utils::IsInt(32, initial_vm_protection));
|
|
ASSERT(Utils::IsInt(32, max_vm_protection));
|
|
ASSERT(strlen(name) <= sizeof(SegmentCommandType::segname));
|
|
}
|
|
|
|
DEFINE_TYPE_CHECK_FOR(MachOSegment)
|
|
|
|
const char* name() const { return name_; }
|
|
const GrowableArray<MachOContents*>& contents() const { return contents_; }
|
|
|
|
bool IsReadable() const {
|
|
return (initial_vm_protection_ & mach_o::VM_PROT_READ) != 0;
|
|
}
|
|
bool IsWritable() const {
|
|
return (initial_vm_protection_ & mach_o::VM_PROT_WRITE) != 0;
|
|
}
|
|
bool IsExecutable() const {
|
|
return (initial_vm_protection_ & mach_o::VM_PROT_EXECUTE) != 0;
|
|
}
|
|
|
|
intptr_t Alignment() const override {
|
|
// TODO(dartbug.com/61973): Use the reduced padding size as the default
|
|
// for native (macOS/iOS) snapshots once the loading issue is resolved, or
|
|
// document why we can't use it for native snapshots loaded by the Dart VM.
|
|
return FLAG_macho_reduce_padding ? 64 : MachOWriter::kPageSize;
|
|
}
|
|
|
|
// The text segment has a file and memory offset of 0, so the superclass's
|
|
// implementations give false negatives after ComputeOffsets.
|
|
bool HasContents() const override { return next_contents_index_ > 0; }
|
|
bool IsAllocated() const override { return true; }
|
|
|
|
bool HasZerofillSections() const {
|
|
return next_contents_index_ != contents_.length();
|
|
}
|
|
|
|
uint32_t cmdsize() const override {
|
|
uword size = sizeof(SegmentCommandType);
|
|
// The header information for sections is nested within the
|
|
// segment load command.
|
|
for (auto* const c : contents_) {
|
|
if (auto* const s = c->AsMachOSection()) {
|
|
size += s->HeaderInfoSize();
|
|
}
|
|
}
|
|
ASSERT(Utils::IsUint(32, size));
|
|
return size;
|
|
}
|
|
|
|
bool PadFileSizeToAlignment() const {
|
|
// The linkedit segment should _not_ be padded to alignment, because
|
|
// that means the code signature isn't the last contents of the file
|
|
// when applicable.
|
|
return !HasName(mach_o::SEG_LINKEDIT);
|
|
}
|
|
|
|
// Segments do not contain any header information, just nested content.
|
|
intptr_t SelfMemorySize() const override { return 0; }
|
|
|
|
intptr_t FileSize() const override {
|
|
intptr_t file_size = SelfFileSize();
|
|
for (auto* const c : contents_) {
|
|
if (!c->HasContents()) continue;
|
|
file_size = Utils::RoundUp(file_size, c->Alignment());
|
|
file_size += c->FileSize();
|
|
}
|
|
if (PadFileSizeToAlignment()) {
|
|
file_size = Utils::RoundUp(file_size, Alignment());
|
|
}
|
|
return AddRequiredPadding(file_size);
|
|
}
|
|
|
|
intptr_t UnpaddedMemorySize() const {
|
|
intptr_t memory_size = SelfMemorySize();
|
|
for (auto* const c : contents_) {
|
|
ASSERT(c->IsAllocated()); // Segments never contain unallocated contents.
|
|
memory_size = Utils::RoundUp(memory_size, c->Alignment());
|
|
memory_size += c->MemorySize();
|
|
}
|
|
return memory_size;
|
|
}
|
|
|
|
intptr_t MemorySize() const override {
|
|
intptr_t memory_size = Utils::RoundUp(UnpaddedMemorySize(), Alignment());
|
|
return AddRequiredPadding(memory_size);
|
|
}
|
|
|
|
// The initial segment of the Mach-O file always includes the header
|
|
// as its first contents.
|
|
bool IsInitial() const { return header() != nullptr; }
|
|
|
|
// Returns the header if this is the initial segment (which contains it),
|
|
// otherwise nullptr.
|
|
const MachOHeader* header() const {
|
|
return contents_.is_empty() ? nullptr : contents_[0]->AsMachOHeader();
|
|
}
|
|
|
|
bool HasName(const char* name) const { return strcmp(name_, name) == 0; }
|
|
|
|
bool ContainsSymbols() const {
|
|
for (auto* const c : contents_) {
|
|
if (auto* const s = c->AsMachOSection()) {
|
|
if (s->ContainsSymbols()) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void AddContents(MachOContents* c);
|
|
|
|
bool IsDebugOnly() const {
|
|
// Currently, the dwarf segment is the only debug-only info we add.
|
|
return HasName(mach_o::SEG_DWARF);
|
|
}
|
|
|
|
void WriteLoadCommand(MachOWriteStream* stream) const override {
|
|
MachOCommand::WriteLoadCommand(stream);
|
|
stream->WriteFixedLengthCString(name_, sizeof(SegmentCommandType::segname));
|
|
stream->WriteWord(memory_address());
|
|
stream->WriteWord(MemorySize());
|
|
stream->WriteWord(file_offset());
|
|
// Only report the actual file size if there is non-header content.
|
|
if (IsInitial() && next_contents_index_ == 1) {
|
|
stream->WriteWord(0);
|
|
} else {
|
|
stream->WriteWord(FileSize());
|
|
}
|
|
stream->Write32(max_vm_protection_);
|
|
stream->Write32(initial_vm_protection_);
|
|
stream->Write32(NumSections());
|
|
// The writer never uses segment flags.
|
|
stream->Write32(0);
|
|
// The load command for a segment also contains descriptions for its
|
|
// sections instead of these being in separate load commands.
|
|
for (auto* const c : contents_) {
|
|
if (!c->IsMachOSection()) continue;
|
|
c->AsMachOSection()->WriteHeaderInfo(stream);
|
|
}
|
|
}
|
|
|
|
MachOSection* FindSection(const char* name, const char* segname) const {
|
|
// Unless this is the unnamed segment in a relocatable object file, there
|
|
// should be no need to check the segment name of the section.
|
|
const bool unnamed = HasName(mach_o::SEG_UNNAMED);
|
|
if (!unnamed && !HasName(segname)) {
|
|
return nullptr;
|
|
}
|
|
for (auto* const c : contents_) {
|
|
if (auto* const s = c->AsMachOSection()) {
|
|
if (s->HasName(name)) {
|
|
ASSERT(unnamed || s->HasSegname(name_));
|
|
if (!unnamed || s->HasSegname(segname)) {
|
|
return s;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
intptr_t NumSections() const {
|
|
intptr_t count = 0;
|
|
for (auto* const c : contents_) {
|
|
if (c->IsMachOSection()) {
|
|
count += 1;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
void Accept(Visitor* visitor) override { visitor->VisitMachOSegment(this); }
|
|
void VisitChildren(Visitor* visitor) override {
|
|
for (auto* const c : contents_) {
|
|
c->Accept(visitor);
|
|
}
|
|
}
|
|
|
|
private:
|
|
intptr_t AddRequiredPadding(intptr_t unpadded_size) const {
|
|
// Pad the text segment to page size if it is encryptable.
|
|
return HasEncryptionInfo(type_) && HasName(mach_o::SEG_TEXT)
|
|
? Utils::RoundUp(unpadded_size, MachOWriter::kPageSize)
|
|
: unpadded_size;
|
|
}
|
|
|
|
const SnapshotType type_;
|
|
const char* const name_;
|
|
bool has_contents_ = false;
|
|
intptr_t next_contents_index_ = 0;
|
|
mach_o::vm_prot_t initial_vm_protection_;
|
|
mach_o::vm_prot_t max_vm_protection_;
|
|
GrowableArray<MachOContents*> contents_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(MachOSegment);
|
|
};
|
|
|
|
class MachOUuid : public MachOCommand {
|
|
public:
|
|
static constexpr uint32_t kCommandCode = mach_o::LC_UUID;
|
|
|
|
explicit MachOUuid(const void* bytes, intptr_t len)
|
|
: MachOCommand(kCommandCode,
|
|
/*needs_offset=*/false,
|
|
/*in_segment=*/false),
|
|
bytes_() {
|
|
// Make sure the length of the byte buffer matches the UUID length, so
|
|
// that the provided UUID isn't unexpectedly truncated or extended.
|
|
ASSERT_EQUAL(len, sizeof(bytes_));
|
|
memmove(bytes_, bytes, sizeof(bytes_));
|
|
}
|
|
|
|
uint32_t cmdsize() const override { return sizeof(mach_o::uuid_command); }
|
|
|
|
void WriteLoadCommand(MachOWriteStream* stream) const override {
|
|
MachOCommand::WriteLoadCommand(stream);
|
|
stream->WriteBytes(bytes_, sizeof(bytes_));
|
|
}
|
|
|
|
void Accept(Visitor* visitor) override { visitor->VisitMachOUuid(this); }
|
|
|
|
private:
|
|
uint8_t bytes_[sizeof(mach_o::uuid_command::uuid)];
|
|
DISALLOW_COPY_AND_ASSIGN(MachOUuid);
|
|
};
|
|
|
|
#define MACHO_XYZ_VERSION_ENCODING(x, y, z) \
|
|
static_cast<uint32_t>(((x) << 16) | ((y) << 8) | (z))
|
|
|
|
class MachODylib : public MachOCommand {
|
|
public:
|
|
uint32_t cmdsize() const override {
|
|
intptr_t size = NameOffset() + strlen(name_) + 1;
|
|
return Utils::RoundUp(size, kLoadCommandAlignment);
|
|
}
|
|
|
|
void WriteLoadCommand(MachOWriteStream* stream) const override {
|
|
MachOCommand::WriteLoadCommand(stream);
|
|
stream->Write32(NameOffset());
|
|
stream->Write32(timestamp_);
|
|
stream->Write32(current_version_);
|
|
stream->Write32(compatibility_version_);
|
|
stream->WriteNullTerminatedCString(name_);
|
|
stream->Align(kLoadCommandAlignment);
|
|
}
|
|
|
|
static constexpr auto kNoVersion = MACHO_XYZ_VERSION_ENCODING(0, 0, 0);
|
|
|
|
protected:
|
|
// This is really an abstract class, with concrete subclasses providing
|
|
// the command code.
|
|
MachODylib(intptr_t cmd,
|
|
const char* name,
|
|
intptr_t timestamp,
|
|
intptr_t current_version = kNoVersion,
|
|
intptr_t compatibility_version = kNoVersion)
|
|
: MachOCommand(cmd,
|
|
/*needs_offset=*/false,
|
|
/*in_segment=*/false),
|
|
name_(ASSERT_NOTNULL(name)),
|
|
timestamp_(timestamp),
|
|
current_version_(current_version),
|
|
compatibility_version_(compatibility_version) {
|
|
ASSERT(Utils::IsUint(32, timestamp));
|
|
ASSERT(Utils::IsUint(32, current_version));
|
|
ASSERT(Utils::IsUint(32, compatibility_version));
|
|
}
|
|
|
|
private:
|
|
uint32_t NameOffset() const { return sizeof(mach_o::dylib_command); }
|
|
|
|
const char* const name_;
|
|
const uint32_t timestamp_;
|
|
const uint32_t current_version_;
|
|
const uint32_t compatibility_version_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(MachODylib);
|
|
};
|
|
|
|
class MachOIdDylib : public MachODylib {
|
|
public:
|
|
static constexpr uint32_t kCommandCode = mach_o::LC_ID_DYLIB;
|
|
|
|
explicit MachOIdDylib(const char* name = kDefaultSnapshotName,
|
|
intptr_t current_version = kNoVersion,
|
|
intptr_t compatibility_version = kNoVersion)
|
|
: MachODylib(kCommandCode,
|
|
name,
|
|
0, // Snapshots aren't copied into user.
|
|
current_version,
|
|
compatibility_version) {}
|
|
|
|
void Accept(Visitor* visitor) override { visitor->VisitMachOIdDylib(this); }
|
|
|
|
private:
|
|
static constexpr char kDefaultSnapshotName[] = "aot.snapshot";
|
|
DISALLOW_COPY_AND_ASSIGN(MachOIdDylib);
|
|
};
|
|
|
|
#if defined(DART_TARGET_OS_MACOS) || defined(DART_TARGET_OS_MACOS_IOS)
|
|
class MachOLoadDylib : public MachODylib {
|
|
public:
|
|
static constexpr uint32_t kCommandCode = mach_o::LC_LOAD_DYLIB;
|
|
|
|
static MachOLoadDylib* CreateLoadSystemDylib(Zone* zone) {
|
|
return new (zone) MachOLoadDylib(kSystemDylibName, 0, kSystemCurrentVersion,
|
|
kSystemCompatVersion);
|
|
}
|
|
|
|
void Accept(Visitor* visitor) override { visitor->VisitMachOLoadDylib(this); }
|
|
|
|
private:
|
|
MachOLoadDylib(const char* name,
|
|
intptr_t timestamp,
|
|
intptr_t current_version,
|
|
intptr_t compatibility_version)
|
|
: MachODylib(kCommandCode,
|
|
name,
|
|
timestamp,
|
|
current_version,
|
|
compatibility_version) {}
|
|
|
|
static constexpr char kSystemDylibName[] = "/usr/lib/libSystem.B.dylib";
|
|
static constexpr auto kSystemCurrentVersion =
|
|
MACHO_XYZ_VERSION_ENCODING(1351, 0, 0);
|
|
static constexpr auto kSystemCompatVersion =
|
|
MACHO_XYZ_VERSION_ENCODING(1, 0, 0);
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(MachOLoadDylib);
|
|
};
|
|
|
|
class Version {
|
|
public:
|
|
explicit Version(intptr_t major) : Version(major, 0, 0) {}
|
|
|
|
Version(intptr_t major, intptr_t minor) : Version(major, minor, 0) {}
|
|
|
|
Version(intptr_t major, intptr_t minor, intptr_t patch)
|
|
: major_(major), minor_(minor), patch_(patch) {
|
|
ASSERT(Utils::IsUint(16, major));
|
|
ASSERT(Utils::IsUint(8, minor));
|
|
ASSERT(Utils::IsUint(8, patch));
|
|
}
|
|
|
|
Version(const Version& other)
|
|
: major_(other.major_), minor_(other.minor_), patch_(other.patch_) {}
|
|
|
|
static Version FromString(const char* str) {
|
|
ASSERT(str != nullptr);
|
|
int64_t major = 0;
|
|
int64_t minor = 0;
|
|
int64_t patch = 0;
|
|
char* current = nullptr;
|
|
if (!OS::ParseInitialInt64(str, &major, ¤t)) {
|
|
FATAL("Expected an integer, got %s", str);
|
|
}
|
|
if (!Utils::IsUint(16, major)) {
|
|
FATAL("Major version is too large to represent in 16 bits: %" Pd64,
|
|
major);
|
|
}
|
|
if (*current != '\0' && *current != '.') {
|
|
FATAL("Unexpected characters when parsing version: %s", current);
|
|
}
|
|
if (*current == '.') {
|
|
if (!OS::ParseInitialInt64(current + 1, &minor, ¤t)) {
|
|
FATAL("Expected an integer, got %s", str);
|
|
}
|
|
if (!Utils::IsUint(8, minor)) {
|
|
FATAL("Minor version is too large to represent in 8 bits: %" Pd64,
|
|
minor);
|
|
}
|
|
if (*current != '\0' && *current != '.') {
|
|
FATAL("Unexpected characters when parsing version: %s", current);
|
|
}
|
|
if (*current == '.') {
|
|
if (!OS::ParseInitialInt64(current + 1, &patch, ¤t)) {
|
|
FATAL("Expected an integer, got %s", str);
|
|
}
|
|
if (!Utils::IsUint(8, patch)) {
|
|
FATAL("Patch version is too large to represent in 8 bits: %" Pd64,
|
|
patch);
|
|
}
|
|
if (*current != '\0') {
|
|
FATAL("Unexpected characters when parsing version: %s", current);
|
|
}
|
|
}
|
|
}
|
|
return Version(major, minor, patch);
|
|
}
|
|
|
|
void Write(MachOWriteStream* stream) const {
|
|
stream->Write32(MACHO_XYZ_VERSION_ENCODING(major_, minor_, patch_));
|
|
}
|
|
|
|
const char* ToCString() const {
|
|
return OS::SCreate(Thread::Current()->zone(), "%" Pd ".%" Pd ".%" Pd "",
|
|
major_, minor_, patch_);
|
|
}
|
|
|
|
private:
|
|
const intptr_t major_;
|
|
const intptr_t minor_;
|
|
const intptr_t patch_;
|
|
DISALLOW_ALLOCATION();
|
|
void operator=(const Version&) = delete;
|
|
};
|
|
|
|
// These defaults were taken from Flutter at the time of editing, but can be
|
|
// overridden using the --min-ios-version and --min-macos-version flags.
|
|
#if defined(DART_TARGET_OS_MACOS_IOS)
|
|
static const Version kDefaultMinOSVersion(13, 0, 0); // iOS 13
|
|
#else
|
|
static const Version kDefaultMinOSVersion(10, 15, 0); // MacOS Catalina (10.15)
|
|
#endif
|
|
|
|
class MachOBuildVersion : public MachOCommand {
|
|
public:
|
|
static constexpr uint32_t kCommandCode = mach_o::LC_BUILD_VERSION;
|
|
|
|
MachOBuildVersion()
|
|
: MachOCommand(kCommandCode,
|
|
/*needs_offset=*/false,
|
|
/*in_segment=*/false),
|
|
min_os_(FLAG_macho_min_os_version != nullptr
|
|
? Version::FromString(FLAG_macho_min_os_version)
|
|
: kDefaultMinOSVersion) {}
|
|
|
|
uint32_t cmdsize() const override {
|
|
return sizeof(mach_o::build_version_command);
|
|
}
|
|
|
|
uint32_t platform() const {
|
|
#if defined(DART_TARGET_OS_MACOS_IOS)
|
|
return mach_o::PLATFORM_IOS;
|
|
#else
|
|
return mach_o::PLATFORM_MACOS;
|
|
#endif
|
|
}
|
|
|
|
const Version& minos() const { return min_os_; }
|
|
|
|
const Version& sdk() const {
|
|
// Just use the minimum version as the targeted version.
|
|
return minos();
|
|
}
|
|
|
|
void WriteLoadCommand(MachOWriteStream* stream) const override {
|
|
MachOCommand::WriteLoadCommand(stream);
|
|
stream->Write32(platform());
|
|
minos().Write(stream);
|
|
sdk().Write(stream);
|
|
stream->Write32(0); // No tool versions.
|
|
}
|
|
|
|
void Accept(Visitor* visitor) override {
|
|
visitor->VisitMachOBuildVersion(this);
|
|
}
|
|
|
|
private:
|
|
const Version min_os_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(MachOBuildVersion);
|
|
};
|
|
|
|
class MachORunPath : public MachOCommand {
|
|
public:
|
|
static constexpr uint32_t kCommandCode = mach_o::LC_RPATH;
|
|
|
|
MachORunPath(const char* path, intptr_t length)
|
|
: MachOCommand(kCommandCode,
|
|
/*needs_offset=*/false,
|
|
/*in_segment=*/false),
|
|
path_(path),
|
|
length_(length) {}
|
|
|
|
uint32_t cmdsize() const override {
|
|
return Utils::RoundUp(HeaderSize() + length_ + 1, kLoadCommandAlignment);
|
|
}
|
|
|
|
void WriteLoadCommand(MachOWriteStream* stream) const override {
|
|
const intptr_t start = stream->Position();
|
|
MachOCommand::WriteLoadCommand(stream);
|
|
stream->Write32(HeaderSize()); // path.offset
|
|
ASSERT_EQUAL(HeaderSize(), stream->Position() - start);
|
|
stream->WriteFixedLengthCString(path_, length_);
|
|
stream->WriteByte('\0'); // Null-terminate the string.
|
|
stream->Align(kLoadCommandAlignment);
|
|
}
|
|
|
|
void Accept(Visitor* visitor) override { visitor->VisitMachORunPath(this); }
|
|
|
|
private:
|
|
uint32_t HeaderSize() const { return sizeof(mach_o::rpath_command); }
|
|
|
|
const char* const path_;
|
|
const intptr_t length_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(MachORunPath);
|
|
};
|
|
#endif
|
|
#undef MACHO_XYZ_VERSION_ENCODING
|
|
|
|
class MachOSymbolTable : public MachOCommand {
|
|
public:
|
|
static constexpr uint32_t kCommandCode = mach_o::LC_SYMTAB;
|
|
|
|
MachOSymbolTable(Zone* zone, bool in_segment)
|
|
: MachOCommand(kCommandCode, /*needs_offset=*/true, in_segment),
|
|
zone_(zone),
|
|
strings_(zone),
|
|
symbols_(zone, 0),
|
|
by_label_index_(zone) {}
|
|
|
|
class StringTable : public ValueObject {
|
|
public:
|
|
explicit StringTable(Zone* zone) : text_(zone), text_indices_(zone) {
|
|
// Ensure the string containing a single space is always at index 0.
|
|
const intptr_t index = Add(" ");
|
|
ASSERT_EQUAL(index, 0);
|
|
// Assign the empty string the index of the null byte in the
|
|
// string added above.
|
|
text_indices_.Insert({"", index + 1});
|
|
}
|
|
|
|
intptr_t Add(const char* str) {
|
|
ASSERT(str != nullptr);
|
|
if (auto const kv = text_indices_.Lookup(str)) {
|
|
return kv->value;
|
|
}
|
|
intptr_t offset = text_.length();
|
|
text_.AddString(str);
|
|
text_.AddChar('\0');
|
|
text_indices_.Insert({str, offset});
|
|
return offset;
|
|
}
|
|
|
|
const char* At(intptr_t index) const {
|
|
if (index >= text_.length()) return nullptr;
|
|
return text_.buffer() + index;
|
|
}
|
|
|
|
intptr_t FileSize() const { return text_.length(); }
|
|
|
|
void Write(MachOWriteStream* stream) const {
|
|
stream->WriteBytes(text_.buffer(), text_.length());
|
|
}
|
|
|
|
private:
|
|
ZoneTextBuffer text_;
|
|
CStringIntMap text_indices_;
|
|
DISALLOW_COPY_AND_ASSIGN(StringTable);
|
|
};
|
|
|
|
struct Symbol {
|
|
Symbol(intptr_t n_idx,
|
|
intptr_t n_type,
|
|
const MachOSection* section,
|
|
intptr_t n_desc,
|
|
uword section_offset_or_value)
|
|
: name_index(n_idx),
|
|
type(n_type),
|
|
section(section),
|
|
description(n_desc),
|
|
section_offset_or_value(section_offset_or_value) {
|
|
ASSERT(Utils::IsUint(32, n_idx));
|
|
ASSERT(Utils::IsUint(8, n_type));
|
|
ASSERT(Utils::IsUint(16, n_desc));
|
|
}
|
|
|
|
void Write(MachOWriteStream* stream) const {
|
|
const intptr_t start = stream->Position();
|
|
stream->Write32(name_index);
|
|
stream->WriteByte(type);
|
|
stream->WriteByte(section_index());
|
|
stream->Write16(description);
|
|
stream->WriteWord(value());
|
|
ASSERT_EQUAL(stream->Position() - start, sizeof(mach_o::nlist));
|
|
}
|
|
|
|
uint8_t section_index() const {
|
|
ASSERT(section == nullptr || Utils::IsUint(8, section->index()));
|
|
return section == nullptr ? mach_o::NO_SECT : section->index();
|
|
}
|
|
|
|
compiler::target::uword value() const {
|
|
const intptr_t base = section != nullptr ? section->memory_address() : 0;
|
|
ASSERT(Utils::IsUint(sizeof(compiler::target::uword) * kBitsPerByte,
|
|
base + section_offset_or_value));
|
|
return base + section_offset_or_value;
|
|
}
|
|
|
|
// The index of the name in the symbol table's string table.
|
|
uint32_t name_index;
|
|
// See the mach_o::N_* constants for the encoding of this field.
|
|
uint8_t type;
|
|
// The section to which this symbol belongs, if any.
|
|
const MachOSection* section;
|
|
// See the mach_o::N_* constants for the encoding of this field.
|
|
uint16_t description;
|
|
// If section == nullptr, then this is the final value of the symbol.
|
|
// Otherwise, it is used to calculate the final value, which can be
|
|
// computed once the section's memory address has been set.
|
|
intptr_t section_offset_or_value;
|
|
|
|
DISALLOW_ALLOCATION();
|
|
};
|
|
|
|
const StringTable& strings() const { return strings_; }
|
|
const GrowableArray<Symbol>& symbols() const { return symbols_; }
|
|
DEBUG_ONLY(intptr_t max_label() const { return max_label_; })
|
|
|
|
void AddSymbol(const char* name,
|
|
intptr_t type,
|
|
const MachOSection* section,
|
|
intptr_t description,
|
|
uword section_offset_or_value,
|
|
intptr_t label = -1) {
|
|
// Section symbols should always have labels, and other symbols
|
|
// (including symbolic debugging symbols) do not.
|
|
if ((type & mach_o::N_STAB) != 0) {
|
|
ASSERT(label <= 0);
|
|
} else {
|
|
ASSERT_EQUAL((type & mach_o::N_TYPE) == mach_o::N_SECT, label > 0);
|
|
}
|
|
ASSERT(!file_offset_is_set()); // Can grow until offsets computed.
|
|
auto const name_index = strings_.Add(name);
|
|
ASSERT(*name == '\0' || name_index != 0);
|
|
const intptr_t new_index = num_symbols();
|
|
symbols_.Add(
|
|
{name_index, type, section, description, section_offset_or_value});
|
|
if (label > 0) {
|
|
DEBUG_ONLY(max_label_ = max_label_ > label ? max_label_ : label);
|
|
// Store an 1-based index since 0 is kNoValue for IntMap.
|
|
by_label_index_.Insert(label, new_index + 1);
|
|
}
|
|
}
|
|
|
|
const Symbol* FindLabel(intptr_t label) const {
|
|
ASSERT(label > 0);
|
|
// The stored index is 1-based.
|
|
const intptr_t symbols_index = IndexForLabel(label);
|
|
if (symbols_index < 0) return nullptr; // Not found.
|
|
return &symbols_[symbols_index];
|
|
}
|
|
|
|
intptr_t IndexForLabel(intptr_t label) const {
|
|
ASSERT(label > 0);
|
|
// The stored index is 1-based.
|
|
return by_label_index_.Lookup(label) - 1;
|
|
}
|
|
|
|
void Initialize(SharedObjectWriter::Type type,
|
|
const char* path,
|
|
const GrowableArray<MachOSection*>& sections,
|
|
bool is_stripped);
|
|
|
|
uint32_t cmdsize() const override { return sizeof(mach_o::symtab_command); }
|
|
|
|
intptr_t SelfMemorySize() const override {
|
|
if (!IsAllocated()) return 0;
|
|
return SelfFileSize();
|
|
}
|
|
|
|
intptr_t SelfFileSize() const override {
|
|
return SymbolsSize() + strings_.FileSize();
|
|
}
|
|
|
|
intptr_t FileSize() const override { return SelfFileSize(); }
|
|
|
|
intptr_t Alignment() const override { return compiler::target::kWordSize; }
|
|
|
|
void WriteLoadCommand(MachOWriteStream* stream) const override {
|
|
MachOCommand::WriteLoadCommand(stream);
|
|
stream->WriteOffsetCount(file_offset(), num_symbols());
|
|
stream->WriteOffsetCount(file_offset() + SymbolsSize(),
|
|
strings_.FileSize());
|
|
}
|
|
|
|
void WriteSelf(MachOWriteStream* stream) const override {
|
|
for (const auto& symbol : symbols_) {
|
|
symbol.Write(stream);
|
|
}
|
|
strings_.Write(stream);
|
|
}
|
|
|
|
intptr_t num_symbols() const { return symbols_.length(); }
|
|
|
|
void Accept(Visitor* visitor) override {
|
|
visitor->VisitMachOSymbolTable(this);
|
|
}
|
|
|
|
#define FOR_EACH_SYMBOL_TABLE_LINEAR_FIELD(M) \
|
|
M(num_local_symbols) \
|
|
M(num_external_symbols)
|
|
|
|
FOR_EACH_SYMBOL_TABLE_LINEAR_FIELD(DEFINE_LINEAR_FIELD_METHODS);
|
|
|
|
private:
|
|
intptr_t SymbolsSize() const { return num_symbols() * sizeof(mach_o::nlist); }
|
|
|
|
Zone* const zone_;
|
|
StringTable strings_;
|
|
GrowableArray<Symbol> symbols_;
|
|
// Maps symbol labels (positive integers) to indexes in symbols_.
|
|
IntMap<intptr_t> by_label_index_;
|
|
DEBUG_ONLY(intptr_t max_label_ = 0;) // For consistency checks.
|
|
|
|
FOR_EACH_SYMBOL_TABLE_LINEAR_FIELD(DEFINE_LINEAR_FIELD);
|
|
#undef FOR_EACH_SYMBOL_TABLE_LINEAR_FIELD
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(MachOSymbolTable);
|
|
};
|
|
|
|
class MachODynamicSymbolTable : public MachOCommand {
|
|
public:
|
|
static constexpr uint32_t kCommandCode = mach_o::LC_DYSYMTAB;
|
|
|
|
MachODynamicSymbolTable(const MachOSymbolTable& table, bool in_segment)
|
|
: MachOCommand(kCommandCode, /*needs_offset=*/true, in_segment),
|
|
table_(table) {}
|
|
|
|
uint32_t cmdsize() const override { return sizeof(mach_o::dysymtab_command); }
|
|
|
|
intptr_t Alignment() const override { return compiler::target::kWordSize; }
|
|
|
|
void WriteLoadCommand(MachOWriteStream* stream) const override {
|
|
MachOCommand::WriteLoadCommand(stream);
|
|
// The symbol table contains local symbols and then external symbols.
|
|
intptr_t index = 0;
|
|
stream->WriteOffsetCount(index, table_.num_local_symbols());
|
|
index += table_.num_local_symbols();
|
|
stream->WriteOffsetCount(index, table_.num_external_symbols());
|
|
index += table_.num_external_symbols();
|
|
// No undefined symbols.
|
|
stream->WriteOffsetCount(index, 0);
|
|
// The rest of the fields are 0-filled.
|
|
for (intptr_t i = 0; i < kUnusedOffsetCountPairs; ++i) {
|
|
stream->WriteOffsetCount(0, 0);
|
|
}
|
|
}
|
|
|
|
// Currently no contents are written to the linkedit segment, as the
|
|
// only non-zero fields are indexes/counts into the symbol table.
|
|
intptr_t SelfMemorySize() const override { return 0; }
|
|
intptr_t SelfFileSize() const override { return 0; }
|
|
intptr_t FileSize() const override { return SelfFileSize(); }
|
|
|
|
void Accept(Visitor* visitor) override {
|
|
visitor->VisitMachODynamicSymbolTable(this);
|
|
}
|
|
|
|
private:
|
|
static constexpr intptr_t kUnusedOffsetCountPairs = 6;
|
|
|
|
const MachOSymbolTable& table_;
|
|
DISALLOW_COPY_AND_ASSIGN(MachODynamicSymbolTable);
|
|
};
|
|
|
|
class MachOEncryptionInfo : public MachOCommand {
|
|
#if defined(TARGET_ARCH_IS_32_BIT)
|
|
using CommandType = mach_o::encryption_info_command;
|
|
#else
|
|
using CommandType = mach_o::encryption_info_command_64;
|
|
#endif
|
|
|
|
public:
|
|
#if defined(TARGET_ARCH_IS_32_BIT)
|
|
static constexpr uint32_t kCommandCode = mach_o::LC_ENCRYPTION_INFO;
|
|
#else
|
|
static constexpr uint32_t kCommandCode = mach_o::LC_ENCRYPTION_INFO_64;
|
|
#endif
|
|
|
|
MachOEncryptionInfo()
|
|
: MachOCommand(kCommandCode,
|
|
/*needs_offset=*/false,
|
|
/*in_segment=*/false) {}
|
|
|
|
uint32_t cmdsize() const override { return sizeof(CommandType); }
|
|
|
|
void WriteLoadCommand(MachOWriteStream* stream) const override {
|
|
MachOCommand::WriteLoadCommand(stream);
|
|
auto [cryptoff, cryptsize] = GetCryptOffsetAndSize(stream);
|
|
stream->WriteOffsetCount(cryptoff, cryptsize);
|
|
stream->Write32(0); // No encryption cypher used.
|
|
#if !defined(TARGET_ARCH_IS_32_BIT)
|
|
stream->Write32(0); // padding.
|
|
#endif
|
|
}
|
|
|
|
void Accept(Visitor* visitor) override {
|
|
visitor->VisitMachOEncryptionInfo(this);
|
|
}
|
|
|
|
private:
|
|
std::pair<uint32_t, uint32_t> GetCryptOffsetAndSize(
|
|
MachOWriteStream* stream) const;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(MachOEncryptionInfo);
|
|
};
|
|
|
|
class MachOLinkEditData : public MachOCommand {
|
|
public:
|
|
uint32_t cmdsize() const override {
|
|
return sizeof(mach_o::linkedit_data_command);
|
|
}
|
|
|
|
void WriteLoadCommand(MachOWriteStream* stream) const override {
|
|
MachOCommand::WriteLoadCommand(stream);
|
|
stream->WriteOffsetCount(file_offset(), FileSize());
|
|
}
|
|
|
|
protected:
|
|
// This is really an abstract class, with concrete subclasses providing
|
|
// the command code.
|
|
explicit MachOLinkEditData(intptr_t cmd)
|
|
: MachOCommand(cmd, /*needs_offset=*/true, /*in_segment=*/true) {}
|
|
|
|
private:
|
|
DISALLOW_COPY_AND_ASSIGN(MachOLinkEditData);
|
|
};
|
|
|
|
class MachOCodeSignature : public MachOLinkEditData {
|
|
public:
|
|
static constexpr uint32_t kCommandCode = mach_o::LC_CODE_SIGNATURE;
|
|
|
|
explicit MachOCodeSignature(const char* identifier)
|
|
: MachOLinkEditData(kCommandCode), identifier_(identifier) {}
|
|
|
|
static constexpr intptr_t kHeaderAlignment = 8;
|
|
static constexpr intptr_t kHashAlignment = 16;
|
|
|
|
intptr_t Alignment() const override { return kHashAlignment; }
|
|
|
|
intptr_t SelfMemorySize() const override {
|
|
return DirectoryOffset() + DirectoryLength();
|
|
}
|
|
|
|
void WriteSelf(MachOWriteStream* stream) const override {
|
|
// The code signature marks the end of the hashed content, as
|
|
// it contains the hashes that ensure the previous content has
|
|
// not been modified (modulo hash collisions).
|
|
stream->FinalizeHashedContent();
|
|
ASSERT_EQUAL(stream->num_hashes(), ExpectedNumHashes());
|
|
const intptr_t start = stream->Position();
|
|
// The superblob header, which includes a single blob index.
|
|
stream->WriteBE32(mach_o::CSMAGIC_EMBEDDED_SIGNATURE); // magic
|
|
stream->WriteBE32(FileSize()); // length
|
|
stream->WriteBE32(1); // count
|
|
// Blob index for the code directory.
|
|
stream->WriteBE32(mach_o::CSSLOT_CODEDIRECTORY); // type
|
|
stream->WriteBE32(DirectoryOffset()); // offset
|
|
stream->Align(kHeaderAlignment);
|
|
// Now the header for the code directory.
|
|
ASSERT_EQUAL(stream->Position() - start, DirectoryOffset());
|
|
const intptr_t directory_start = stream->Position();
|
|
stream->WriteBE32(mach_o::CSMAGIC_CODEDIRECTORY); // magic
|
|
stream->WriteBE32(DirectoryLength()); // length
|
|
stream->WriteBE32(mach_o::CS_SUPPORTSEXECSEG); // version
|
|
stream->WriteBE32(mach_o::CS_ADHOC | mach_o::CS_LINKER_SIGNED); // flags
|
|
stream->WriteBE32(HashOffset());
|
|
stream->WriteBE32(IdentOffset());
|
|
stream->WriteBE32(0); // num special slots (hashes)
|
|
stream->WriteBE32(stream->num_hashes()); // num code slots (hashes)
|
|
stream->WriteBE32(file_offset()); // code limit
|
|
stream->WriteByte(MachOWriteStream::kHashSize);
|
|
stream->WriteByte(MachOWriteStream::kHashType);
|
|
stream->WriteByte(0); // platform
|
|
// The page size is represented by its base 2 logarithm.
|
|
stream->WriteByte(Utils::ShiftForPowerOfTwo(MachOWriteStream::kChunkSize));
|
|
stream->WriteBE32(0); // spare2 (always 0)
|
|
// version >= 0x20100 (CS_SUPPORTSSCATTER)
|
|
stream->WriteBE32(0); // scatter offset
|
|
// version >= 0x20200 (CS_SUPPORTSTEAMID)
|
|
stream->WriteBE32(0); // teamid offset
|
|
// version >= 0x20300 (CS_SUPPORTSCODELIMIT64)
|
|
stream->WriteBE32(0); // spare3 (always 0)
|
|
stream->WriteBE64(0); // code limit (64-bit)
|
|
// version >= 0x20400 (CS_SUPPORTSEXECSEG)
|
|
stream->WriteBE64(stream->TextSegment().file_offset()); // offset
|
|
stream->WriteBE64(stream->TextSegment().FileSize()); // limit
|
|
stream->WriteBE64(0); // flags
|
|
stream->Align(kHeaderAlignment);
|
|
ASSERT_EQUAL(stream->Position() - directory_start, IdentOffset());
|
|
stream->WriteFixedLengthCString(identifier_, strlen(identifier_) + 1);
|
|
stream->Align(kHashAlignment);
|
|
ASSERT_EQUAL(stream->Position() - directory_start, HashOffset());
|
|
stream->WriteHashes();
|
|
ASSERT_EQUAL(stream->Position() - directory_start, DirectoryLength());
|
|
}
|
|
|
|
void Accept(Visitor* visitor) override {
|
|
visitor->VisitMachOCodeSignature(this);
|
|
}
|
|
|
|
private:
|
|
// The offset of the code directory in the code signature.
|
|
intptr_t DirectoryOffset() const {
|
|
// A single blob index for the code directory.
|
|
const intptr_t offset =
|
|
sizeof(mach_o::cs_superblob) + sizeof(mach_o::cs_blob_index);
|
|
return Utils::RoundUp(offset, kHeaderAlignment);
|
|
}
|
|
|
|
intptr_t DirectoryLength() const {
|
|
return HashOffset() + ExpectedNumHashes() * MachOWriteStream::kHashSize;
|
|
}
|
|
|
|
// The offset of the identifier within the code directory.
|
|
intptr_t IdentOffset() const {
|
|
// Include the directory offset to ensure proper alignment, but the
|
|
// returned value is relative to the code directory start.
|
|
intptr_t signature_offset =
|
|
DirectoryOffset() + sizeof(mach_o::cs_code_directory);
|
|
return Utils::RoundUp(signature_offset, kHeaderAlignment) -
|
|
DirectoryOffset();
|
|
}
|
|
|
|
// The offset of the list of hashes within the code directory.
|
|
intptr_t HashOffset() const {
|
|
// Include the directory offset to ensure proper alignment, but the
|
|
// returned value is relative to the code directory start.
|
|
const intptr_t signature_offset =
|
|
DirectoryOffset() + IdentOffset() + strlen(identifier_) + 1;
|
|
return Utils::RoundUp(signature_offset, kHashAlignment) - DirectoryOffset();
|
|
}
|
|
|
|
intptr_t ExpectedNumHashes() const {
|
|
// The actual hashes are stored in the stream, which isn't available yet.
|
|
// However, if the file offsets of the code signature has been computed, the
|
|
// number of hashes that should be contained in the stream can be computed.
|
|
const intptr_t chunk_size = MachOWriteStream::kChunkSize;
|
|
return (file_offset() + chunk_size - 1) / chunk_size;
|
|
}
|
|
|
|
const char* const identifier_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(MachOCodeSignature);
|
|
};
|
|
|
|
// A representation of the header of the Mach-O file. This contains
|
|
// any commands that have load commands within the header.
|
|
class MachOHeader : public MachOContents {
|
|
#if defined(TARGET_ARCH_IS_32_BIT)
|
|
using HeaderType = mach_o::mach_header;
|
|
#else
|
|
using HeaderType = mach_o::mach_header_64;
|
|
#endif
|
|
|
|
using SnapshotType = SharedObjectWriter::Type;
|
|
|
|
public:
|
|
MachOHeader(Zone* zone,
|
|
SnapshotType type,
|
|
bool is_stripped,
|
|
bool has_separate_object,
|
|
const char* identifier,
|
|
const char* path,
|
|
Dwarf* dwarf)
|
|
: MachOContents(/*needs_offset=*/true,
|
|
/*in_segment=*/type != SnapshotType::Object),
|
|
zone_(zone),
|
|
type_(type),
|
|
is_stripped_(is_stripped),
|
|
has_separate_object_(has_separate_object),
|
|
identifier_(identifier != nullptr ? identifier : ""),
|
|
path_(path),
|
|
dwarf_(dwarf),
|
|
commands_(zone, 0),
|
|
full_symtab_(zone, /*in_segment=*/type != SnapshotType::Object) {
|
|
#if defined(DART_TARGET_OS_MACOS)
|
|
// A non-nullptr identifier must be provided for MacOS targets.
|
|
ASSERT(identifier != nullptr);
|
|
#endif
|
|
// Unstripped content must have DWARF information available.
|
|
ASSERT(dwarf != nullptr || is_stripped_);
|
|
// Only snapshots should be stripped.
|
|
ASSERT(!is_stripped_ || type == SnapshotType::Snapshot);
|
|
}
|
|
|
|
DEFINE_TYPE_CHECK_FOR(MachOHeader)
|
|
|
|
Zone* zone() const { return zone_; }
|
|
const GrowableArray<MachOCommand*>& commands() const { return commands_; }
|
|
const MachOSymbolTable& relocation_symbol_table() const {
|
|
return full_symtab_;
|
|
}
|
|
const MachOSegment& text_segment() const {
|
|
ASSERT(text_segment_ != nullptr);
|
|
return *text_segment_;
|
|
}
|
|
SharedObjectWriter::Type type() const { return type_; }
|
|
|
|
intptr_t NumSections() const {
|
|
intptr_t num_sections = 0;
|
|
for (auto* const command : commands()) {
|
|
if (auto* const s = command->AsMachOSegment()) {
|
|
num_sections += s->NumSections();
|
|
}
|
|
}
|
|
return num_sections;
|
|
}
|
|
|
|
// The contents of the header is always at offset/address 0, so the
|
|
// superclass's check returns a false negative here after ComputeOffsets.
|
|
bool HasContents() const override { return true; }
|
|
bool IsAllocated() const override { return type_ != SnapshotType::Object; }
|
|
intptr_t Alignment() const override { return compiler::target::kWordSize; }
|
|
|
|
// This should be used if the size of the header without the load commands
|
|
// is desired.
|
|
intptr_t SizeWithoutLoadCommands() const {
|
|
const intptr_t size = sizeof(HeaderType);
|
|
ASSERT(Utils::IsAligned(size, MachOCommand::kLoadCommandAlignment));
|
|
return size;
|
|
}
|
|
|
|
intptr_t SelfMemorySize() const override {
|
|
if (!IsAllocated()) return 0;
|
|
return SelfFileSize();
|
|
}
|
|
|
|
intptr_t MemorySize() const override {
|
|
if (!IsAllocated()) return 0;
|
|
return AddRequiredPadding(SelfMemorySize());
|
|
}
|
|
|
|
intptr_t SelfFileSize() const override {
|
|
intptr_t size = SizeWithoutLoadCommands();
|
|
for (auto* const command : commands_) {
|
|
size += command->cmdsize();
|
|
}
|
|
return size;
|
|
}
|
|
|
|
intptr_t FileSize() const override {
|
|
return AddRequiredPadding(SelfFileSize());
|
|
}
|
|
|
|
uint32_t filetype() const {
|
|
switch (type_) {
|
|
case SnapshotType::Snapshot:
|
|
return mach_o::MH_DYLIB;
|
|
case SnapshotType::DebugInfo:
|
|
return mach_o::MH_DSYM;
|
|
case SnapshotType::Object:
|
|
return mach_o::MH_OBJECT;
|
|
default:
|
|
UNREACHABLE();
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
uint32_t flags() const {
|
|
if (type_ == SnapshotType::Snapshot) {
|
|
return mach_o::MH_NOUNDEFS | mach_o::MH_DYLDLINK |
|
|
mach_o::MH_NO_REEXPORTED_DYLIBS | mach_o::MH_TWOLEVEL;
|
|
}
|
|
ASSERT(type_ == SnapshotType::DebugInfo || type_ == SnapshotType::Object);
|
|
return 0;
|
|
}
|
|
|
|
mach_o::cpu_type_t cpu_type() const {
|
|
#if defined(TARGET_ARCH_X64)
|
|
return mach_o::CPU_TYPE_X86_64;
|
|
#elif defined(TARGET_ARCH_ARM64)
|
|
return mach_o::CPU_TYPE_ARM64;
|
|
#elif defined(TARGET_ARCH_IA32)
|
|
return mach_o::CPU_TYPE_I386;
|
|
#elif defined(TARGET_ARCH_ARM)
|
|
return mach_o::CPU_TYPE_ARM;
|
|
#else
|
|
// This architecture doesn't have specific constants defined in
|
|
// <mach/machine.h>, so just mark it as ANY since the snapshot
|
|
// header check also catches architecture mismatches.
|
|
return mach_o::CPU_TYPE_ANY;
|
|
#endif
|
|
}
|
|
|
|
mach_o::cpu_subtype_t cpu_subtype() const {
|
|
#if defined(TARGET_ARCH_X64)
|
|
return mach_o::CPU_SUBTYPE_X86_64_ALL;
|
|
#elif defined(TARGET_ARCH_ARM64)
|
|
return mach_o::CPU_SUBTYPE_ARM64_ALL;
|
|
#elif defined(TARGET_ARCH_IA32)
|
|
return mach_o::CPU_SUBTYPE_I386_ALL;
|
|
#elif defined(TARGET_ARCH_ARM)
|
|
return mach_o::CPU_SUBTYPE_ARM_ALL;
|
|
#else
|
|
// This architecture doesn't have specific constants defined in
|
|
// <mach/machine.h>, so just mark it as ANY since the snapshot
|
|
// header check also catches architecture mismatches.
|
|
return mach_o::CPU_SUBTYPE_ANY;
|
|
#endif
|
|
}
|
|
|
|
void WriteSelf(MachOWriteStream* stream) const override {
|
|
intptr_t start = stream->Position();
|
|
ASSERT_EQUAL(start, 0);
|
|
#if defined(TARGET_ARCH_IS_32_BIT)
|
|
stream->Write32(mach_o::MH_MAGIC);
|
|
#else
|
|
stream->Write32(mach_o::MH_MAGIC_64);
|
|
#endif
|
|
stream->Write32(cpu_type());
|
|
stream->Write32(cpu_subtype());
|
|
stream->Write32(filetype());
|
|
stream->Write32(commands_.length());
|
|
uint32_t sizeofcmds = 0;
|
|
for (auto* const command : commands_) {
|
|
sizeofcmds += command->cmdsize();
|
|
}
|
|
stream->Write32(sizeofcmds);
|
|
stream->Write32(flags());
|
|
#if !defined(TARGET_ARCH_IS_32_BIT)
|
|
stream->Write32(0); // Reserved field.
|
|
#endif
|
|
ASSERT_EQUAL(stream->Position() - start, sizeof(HeaderType));
|
|
for (auto* const command : commands_) {
|
|
const intptr_t load_start = stream->Position();
|
|
ASSERT_EQUAL(load_start, start + command->header_offset());
|
|
command->WriteLoadCommand(stream);
|
|
ASSERT_EQUAL(stream->Position() - load_start,
|
|
static_cast<intptr_t>(command->cmdsize()));
|
|
}
|
|
}
|
|
|
|
// Returns the command with the given concrete subclass of MachOCommand
|
|
// (that is, a subclass that defines a kCommandCode constant). Should only
|
|
// be used for commands that appear at most once (e.g., not segments).
|
|
template <typename T>
|
|
T* FindCommand() const {
|
|
return reinterpret_cast<T*>(FindCommand(T::kCommandCode));
|
|
}
|
|
|
|
// Returns the command with the given command code. Should only be used
|
|
// for commands that appear at most once (e.g., not segments).
|
|
MachOCommand* FindCommand(uint32_t cmd) const {
|
|
MachOCommand* result = nullptr;
|
|
for (auto* const command : commands_) {
|
|
if (command->cmd() == cmd) {
|
|
ASSERT(result == nullptr);
|
|
result = command;
|
|
#if !defined(DEBUG)
|
|
break; // No checking, so don't continue iterating.
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Returns whether there is a command has the given command code.
|
|
bool HasCommand(uint32_t cmd) const {
|
|
for (auto* const command : commands_) {
|
|
if (command->cmd() == cmd) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Returns the segment with name [name] or nullptr if there is none.
|
|
MachOSegment* FindSegment(const char* name) const {
|
|
for (auto* const command : commands_) {
|
|
if (auto* const s = command->AsMachOSegment()) {
|
|
if (s->HasName(name)) return s;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
// Returns the section with name [sectname] in segment [segname]
|
|
// or nullptr if there is none.
|
|
MachOSection* FindSection(const char* segname, const char* sectname) const {
|
|
// All sections are in the unnamed segment for object files.
|
|
auto* const segment =
|
|
type_ == SnapshotType::Object ? text_segment_ : FindSegment(segname);
|
|
if (segment == nullptr) return nullptr;
|
|
return segment->FindSection(sectname, segname);
|
|
}
|
|
|
|
MachOSegment* EnsureTextSegment() {
|
|
if (text_segment_ == nullptr) {
|
|
// For relocatable objects, all sections are put into a single unnamed
|
|
// segment.
|
|
auto* const name = type_ == SnapshotType::Object ? mach_o::SEG_UNNAMED
|
|
: mach_o::SEG_TEXT;
|
|
// Make sure it didn't get added outside this method.
|
|
ASSERT(FindSegment(name) == nullptr);
|
|
auto const vm_protection =
|
|
type_ == SnapshotType::Object
|
|
? mach_o::VM_PROT_ALL
|
|
: mach_o::VM_PROT_READ | mach_o::VM_PROT_EXECUTE;
|
|
text_segment_ = new (zone())
|
|
MachOSegment(zone(), type_, name, vm_protection, vm_protection);
|
|
commands_.Add(text_segment_);
|
|
}
|
|
return text_segment_;
|
|
}
|
|
|
|
void Finalize();
|
|
|
|
void Accept(Visitor* visitor) override { visitor->VisitMachOHeader(this); }
|
|
|
|
// Since the header is in the initial segment for most snapshot types,
|
|
// visiting the load commands here and also visiting the header in
|
|
// MachOSegment::VisitChildren() would cause a cycle if, say, Default()
|
|
// is overridden to be recursive. Thus, the default VisitChildren
|
|
// implementation here does no recursion.
|
|
void VisitChildren(Visitor* visitor) override {}
|
|
void VisitContents(Visitor* visitor) {
|
|
if (type_ == SnapshotType::Object) {
|
|
// The header is visited during the initial segment for other types.
|
|
Accept(visitor);
|
|
}
|
|
for (auto* const c : commands_) {
|
|
if (type_ != SnapshotType::Object) {
|
|
// All commands with non-header content should be part of a segment.
|
|
if (!c->IsMachOSegment()) continue;
|
|
}
|
|
c->Accept(visitor);
|
|
}
|
|
}
|
|
|
|
// Returns the symbol table that is included in the output, which
|
|
// may or may not be the full symbol table.
|
|
//
|
|
// Returns nullptr if called before symbol table initialization.
|
|
const MachOSymbolTable* IncludedSymbolTable() const {
|
|
return const_cast<MachOHeader*>(this)->IncludedSymbolTable();
|
|
}
|
|
|
|
private:
|
|
intptr_t AddRequiredPadding(intptr_t unpadded_size) const {
|
|
// If the text segment contents can be encrypted, pad the header
|
|
// to page size so that its pages are entirely excluded
|
|
// from the file contents covered by LC_ENCRYPTION_INFO.
|
|
return HasEncryptionInfo(type_)
|
|
? Utils::RoundUp(unpadded_size, MachOWriter::kPageSize)
|
|
: unpadded_size;
|
|
}
|
|
|
|
void GenerateUuid();
|
|
void CreateBSS();
|
|
void GenerateUnwindingInformation();
|
|
void GenerateMiscellaneousCommands();
|
|
void InitializeSymbolTables();
|
|
void FinalizeDwarfSections();
|
|
void FinalizeCommands();
|
|
void ComputeOffsets();
|
|
|
|
#if defined(DART_TARGET_OS_MACOS) && defined(TARGET_ARCH_ARM64)
|
|
void GenerateCompactUnwindingInformation(
|
|
DwarfSharedObjectStream& stream,
|
|
const GrowableArray<Dwarf::FrameDescriptionEntry>& fdes);
|
|
#endif
|
|
|
|
// Returns the symbol table that is included in the output, which
|
|
// may or may not be the full symbol table.
|
|
//
|
|
// Returns nullptr if called before symbol table initialization.
|
|
MachOSymbolTable* IncludedSymbolTable() {
|
|
// True when the symbol tables haven't been initialized.
|
|
if (full_symtab_.symbols().is_empty()) return nullptr;
|
|
// The full symbol table is reused for unstripped contents.
|
|
if (!is_stripped_) return &full_symtab_;
|
|
return FindCommand<MachOSymbolTable>();
|
|
}
|
|
|
|
Zone* const zone_;
|
|
const SnapshotType type_;
|
|
// Used to determine whether to include non-global symbols in the
|
|
// symbol table written to disk.
|
|
bool const is_stripped_;
|
|
// Whether this is a snapshot that has an associated relocatable object
|
|
// emitted.
|
|
bool const has_separate_object_;
|
|
// The identifier, used in the LC_ID_DYLIB command and the code signature.
|
|
const char* const identifier_;
|
|
// The absolute path, used to create an N_OSO symbolic debugging variable
|
|
// in unstripped snapshots.
|
|
const char* const path_;
|
|
Dwarf* const dwarf_;
|
|
GrowableArray<MachOCommand*> commands_;
|
|
// Contains all symbols for relocation calculations.
|
|
MachOSymbolTable full_symtab_;
|
|
// For relocatable objects, the "text" segment is the unnamed segment that
|
|
// holds all sections. Otherwise, it is the text segment as expected.
|
|
MachOSegment* text_segment_ = nullptr;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(MachOHeader);
|
|
};
|
|
|
|
void MachOSegment::AddContents(MachOContents* c) {
|
|
ASSERT(c != nullptr);
|
|
// Segment contents are always allocated.
|
|
ASSERT(c->IsAllocated());
|
|
// Only sections should be added to the unnamed segment.
|
|
ASSERT(!HasName(mach_o::SEG_UNNAMED) || c->IsMachOSection());
|
|
// The order of segment contents is as follows:
|
|
// 1) The header (if this is the initial segment).
|
|
// 2) Content-containing sections and commands (in the linkedit segment).
|
|
// 3) Sections without contents like zerofill sections.
|
|
if (c->IsMachOHeader()) {
|
|
ASSERT(c->AsMachOHeader()->commands()[0] == this);
|
|
contents_.InsertAt(0, c);
|
|
next_contents_index_ += 1;
|
|
} else if (c->HasContents()) {
|
|
ASSERT_EQUAL(c->IsMachOCommand(), HasName(mach_o::SEG_LINKEDIT));
|
|
contents_.InsertAt(next_contents_index_, c);
|
|
next_contents_index_ += 1;
|
|
} else {
|
|
ASSERT(c->IsMachOSection());
|
|
contents_.Add(c);
|
|
}
|
|
}
|
|
|
|
bool MachOWriteStream::HasValueForLabel(intptr_t label, intptr_t* value) const {
|
|
ASSERT(value != nullptr);
|
|
const auto& header = macho_.header();
|
|
if (label == SharedObjectWriter::kBuildIdLabel) {
|
|
// Unlike ELF, the uuid is not in a MachO section and so can't have a symbol
|
|
// assigned. Instead, we look up its load command offset in the header.
|
|
auto* const uuid = header.FindCommand<MachOUuid>();
|
|
if (uuid == nullptr) return false;
|
|
*value = header.file_offset() + uuid->header_offset();
|
|
return true;
|
|
}
|
|
const auto& symtab = header.relocation_symbol_table();
|
|
auto* const symbol = symtab.FindLabel(label);
|
|
if (symbol == nullptr) return false;
|
|
*value = symbol->value();
|
|
return true;
|
|
}
|
|
|
|
std::pair<uint32_t, uint32_t> MachOEncryptionInfo::GetCryptOffsetAndSize(
|
|
MachOWriteStream* stream) const {
|
|
const auto& header = stream->Header();
|
|
const auto& text_segment = header.text_segment();
|
|
const uint32_t offset = header.FileSize();
|
|
const uint32_t size = text_segment.FileSize() - offset;
|
|
// Both the offset and size should be a multiple of the page size.
|
|
ASSERT((offset % MachOWriter::kPageSize) == 0);
|
|
ASSERT((size % MachOWriter::kPageSize) == 0);
|
|
return {offset, size};
|
|
}
|
|
|
|
const MachOHeader& MachOWriteStream::Header() const {
|
|
return macho_.header();
|
|
}
|
|
|
|
const MachOSegment& MachOWriteStream::TextSegment() const {
|
|
return macho_.header().text_segment();
|
|
}
|
|
|
|
MachOWriter::MachOWriter(Zone* zone,
|
|
BaseWriteStream* stream,
|
|
Type type,
|
|
const char* id,
|
|
const char* path,
|
|
Dwarf* dwarf,
|
|
MachOWriter* object_writer)
|
|
: SharedObjectWriter(zone, stream, type, dwarf),
|
|
object_writer_(object_writer),
|
|
header_(*new (zone) MachOHeader(
|
|
zone,
|
|
type,
|
|
IsStripped(dwarf),
|
|
type == SharedObjectWriter::Type::Snapshot &&
|
|
object_writer != nullptr,
|
|
FLAG_macho_install_name != nullptr ? FLAG_macho_install_name : id,
|
|
path,
|
|
dwarf)) {
|
|
ASSERT(type == Type::Snapshot || object_writer == nullptr);
|
|
}
|
|
|
|
void MachOWriter::AddText(const char* name,
|
|
intptr_t label,
|
|
const uint8_t* bytes,
|
|
intptr_t size,
|
|
const ZoneGrowableArray<Relocation>* relocations,
|
|
const ZoneGrowableArray<SymbolData>* symbols) {
|
|
auto* const text_segment = header_.EnsureTextSegment();
|
|
auto* text_section =
|
|
text_segment->FindSection(mach_o::SECT_TEXT, mach_o::SEG_TEXT);
|
|
if (text_section == nullptr) {
|
|
const bool has_contents = type_ != Type::DebugInfo;
|
|
const intptr_t attributes =
|
|
mach_o::S_ATTR_PURE_INSTRUCTIONS | mach_o::S_ATTR_SOME_INSTRUCTIONS;
|
|
text_section = new (zone()) MachOSection(
|
|
zone(), mach_o::SECT_TEXT, mach_o::SEG_TEXT, text_segment->Alignment(),
|
|
mach_o::S_REGULAR, attributes, has_contents);
|
|
text_segment->AddContents(text_section);
|
|
}
|
|
text_section->AddPortion(bytes, size, relocations, symbols, name, label);
|
|
if (object_writer_ != nullptr) {
|
|
object_writer_->AddText(name, label, bytes, size, relocations, symbols);
|
|
}
|
|
}
|
|
|
|
void MachOWriter::AddROData(const char* name,
|
|
intptr_t label,
|
|
const uint8_t* bytes,
|
|
intptr_t size,
|
|
const ZoneGrowableArray<Relocation>* relocations,
|
|
const ZoneGrowableArray<SymbolData>* symbols) {
|
|
// Const data goes in the text segment, not the data one.
|
|
auto* const text_segment = header_.EnsureTextSegment();
|
|
auto* const_section =
|
|
text_segment->FindSection(mach_o::SECT_CONST, mach_o::SEG_TEXT);
|
|
if (const_section == nullptr) {
|
|
const bool has_contents = type_ != Type::DebugInfo;
|
|
const_section = new (zone()) MachOSection(
|
|
zone(), mach_o::SECT_CONST, mach_o::SEG_TEXT, text_segment->Alignment(),
|
|
mach_o::S_REGULAR, mach_o::S_NO_ATTRIBUTES, has_contents);
|
|
text_segment->AddContents(const_section);
|
|
}
|
|
const_section->AddPortion(bytes, size, relocations, symbols, name, label);
|
|
if (object_writer_ != nullptr) {
|
|
object_writer_->AddROData(name, label, bytes, size, relocations, symbols);
|
|
}
|
|
}
|
|
|
|
class WriteVisitor : public MachOContents::Visitor {
|
|
public:
|
|
explicit WriteVisitor(MachOWriteStream* stream) : stream_(stream) {}
|
|
|
|
void Default(MachOContents* contents) override {
|
|
if (!contents->HasContents()) return;
|
|
stream_->Align(contents->Alignment());
|
|
const intptr_t start = stream_->Position();
|
|
ASSERT_EQUAL(start, contents->file_offset());
|
|
contents->WriteSelf(stream_);
|
|
ASSERT_EQUAL(stream_->Position() - start, contents->SelfFileSize());
|
|
contents->VisitChildren(this);
|
|
// Segments include post-nested content alignment.
|
|
if (auto* const s = contents->AsMachOSegment()) {
|
|
if (s->PadFileSizeToAlignment()) {
|
|
stream_->Align(contents->Alignment());
|
|
}
|
|
}
|
|
ASSERT_EQUAL(stream_->Position() - start, contents->FileSize());
|
|
}
|
|
|
|
private:
|
|
MachOWriteStream* stream_;
|
|
DISALLOW_COPY_AND_ASSIGN(WriteVisitor);
|
|
};
|
|
|
|
class WriteRelocationsVisitor : public MachOContents::Visitor {
|
|
public:
|
|
explicit WriteRelocationsVisitor(MachOWriteStream* stream)
|
|
: stream_(stream) {}
|
|
|
|
void Default(MachOContents* contents) override {}
|
|
|
|
void VisitMachOSegment(MachOSegment* segment) override {
|
|
segment->VisitChildren(this);
|
|
}
|
|
|
|
void VisitMachOSection(MachOSection* section) override {
|
|
if (auto* const relocations = section->relocations()) {
|
|
ASSERT_EQUAL(stream_->Position(), section->relocations_file_offset());
|
|
for (const auto& reloc : *relocations) {
|
|
stream_->Write32(reloc.address);
|
|
stream_->Write32(reloc.metadata);
|
|
}
|
|
} else {
|
|
ASSERT_EQUAL(section->relocations_file_offset(), 0);
|
|
}
|
|
}
|
|
|
|
private:
|
|
MachOWriteStream* stream_;
|
|
DISALLOW_COPY_AND_ASSIGN(WriteRelocationsVisitor);
|
|
};
|
|
|
|
void MachOWriter::Finalize() {
|
|
header_.Finalize();
|
|
if (header_.HasCommand(MachOCodeSignature::kCommandCode)) {
|
|
HashingMachOWriteStream wrapped(zone_, unwrapped_stream_, *this);
|
|
WriteVisitor visitor(&wrapped);
|
|
header_.VisitContents(&visitor);
|
|
// Relocatable objects aren't signed, so no relocations to write.
|
|
} else {
|
|
NonHashingMachOWriteStream wrapped(unwrapped_stream_, *this);
|
|
WriteVisitor visitor(&wrapped);
|
|
header_.VisitContents(&visitor);
|
|
if (type_ == SharedObjectWriter::Type::Object) {
|
|
WriteRelocationsVisitor reloc_visitor(&wrapped);
|
|
header_.VisitContents(&reloc_visitor);
|
|
}
|
|
}
|
|
if (object_writer_ != nullptr) {
|
|
object_writer_->Finalize();
|
|
}
|
|
}
|
|
|
|
void MachOWriter::AssertConsistency(const SharedObjectWriter* debug) const {
|
|
if (FLAG_macho_reduce_padding) {
|
|
// TODO(sstrickl): This currently fails because the reduced padding
|
|
// and difference in header sizes means the virtual addresses won't
|
|
// align (though symbolicizing the symbol+offset (PCOffset) information
|
|
// in traces still gives appropriately matching information).
|
|
//
|
|
// However, the only usecase for this reduced padding creates a .dSYM
|
|
// for symbolization instead of using the separate debug info, so
|
|
// ignore this mismatch for now.
|
|
return;
|
|
}
|
|
if (auto* const debug_macho = debug->AsMachOWriter()) {
|
|
AssertConsistency(this, debug_macho);
|
|
} else {
|
|
FATAL("Expected both snapshot and debug to be MachO");
|
|
}
|
|
}
|
|
|
|
void MachOHeader::Finalize() {
|
|
// Generate the UUID now that we have all user-provided sections.
|
|
GenerateUuid();
|
|
|
|
// We add a BSS section for all Mach-O output with text sections, even in
|
|
// the separate debugging information, to ensure that relocated addresses
|
|
// are consistent between snapshots and the corresponding separate
|
|
// debugging information.
|
|
CreateBSS();
|
|
|
|
FinalizeDwarfSections();
|
|
|
|
// Generate miscellenous load commands needed for the final output.
|
|
GenerateMiscellaneousCommands();
|
|
|
|
// Generate appropriate unwinding information for the target platform,
|
|
// for example, unwinding records on Windows.
|
|
GenerateUnwindingInformation();
|
|
|
|
// Initialize both the static and dynamic symbol tables. Calls to methods
|
|
// that change section numbering (by either adding or reordering sections
|
|
// and/or segments) after this point must update the section numbers on
|
|
// section symbols to match.
|
|
InitializeSymbolTables();
|
|
|
|
// Reorders the added commands as well as adding segments and commands
|
|
// that must appear at the end of the file.
|
|
FinalizeCommands();
|
|
|
|
// Calculate file and memory offsets, and finalizes symbol values in any
|
|
// symbol tables.
|
|
ComputeOffsets();
|
|
}
|
|
|
|
void MachOWriter::AssertConsistency(const MachOWriter* snapshot,
|
|
const MachOWriter* debug_info) {
|
|
#if defined(DEBUG)
|
|
// For now, just check that the symbol information for both match
|
|
// in that all labelled symbols used for relocation have the same
|
|
// value.
|
|
const auto& snapshot_symtab = snapshot->header().relocation_symbol_table();
|
|
const auto& debug_info_symtab =
|
|
debug_info->header().relocation_symbol_table();
|
|
|
|
intptr_t max_label = snapshot_symtab.max_label();
|
|
ASSERT_EQUAL(max_label, debug_info_symtab.max_label());
|
|
for (intptr_t i = 1; i < max_label; ++i) {
|
|
if (auto* const snapshot_symbol = snapshot_symtab.FindLabel(i)) {
|
|
auto* const debug_info_symbol = debug_info_symtab.FindLabel(i);
|
|
ASSERT(debug_info_symbol != nullptr);
|
|
if (snapshot_symbol->value() != debug_info_symbol->value()) {
|
|
FATAL("Snapshot: %s -> %" Px64 ", %s -> %" Px64 "",
|
|
snapshot_symtab.strings().At(snapshot_symbol->name_index),
|
|
static_cast<uint64_t>(snapshot_symbol->value()),
|
|
debug_info_symtab.strings().At(debug_info_symbol->name_index),
|
|
static_cast<uint64_t>(debug_info_symbol->value()));
|
|
}
|
|
} else {
|
|
ASSERT(debug_info_symtab.FindLabel(i) == nullptr);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static uint64_t HashPortion(const MachOSection::Portion& portion) {
|
|
if (portion.bytes == nullptr) return 0;
|
|
const uint64_t hash = Utils::StringHash64(portion.bytes, portion.size);
|
|
// Ensure a non-zero return.
|
|
return hash == 0 ? 1 : hash;
|
|
}
|
|
|
|
// For the UUID, we generate a 128-bit hash, where each 64 bits is a
|
|
// hash of the contents of the following segments in order:
|
|
//
|
|
// .text(Isolate) | .rodata(Isolate)
|
|
void MachOHeader::GenerateUuid() {
|
|
// Don't create a UUID for a relocatable object.
|
|
if (type_ == SnapshotType::Object) return;
|
|
// Not idempotent.
|
|
ASSERT(!HasCommand(MachOUuid::kCommandCode));
|
|
// Currently, we construct the UUID out of data from two different
|
|
// sections in the text segment: the text section and the const section.
|
|
if (text_segment_ == nullptr) return;
|
|
|
|
auto* const text_section =
|
|
text_segment_->FindSection(mach_o::SECT_TEXT, mach_o::SEG_TEXT);
|
|
// If there is no text section, then a UUID is not needed, as it is only
|
|
// used to symbolicize non-symbolic stack traces.
|
|
if (text_section == nullptr) return;
|
|
|
|
auto* const isolate_instructions =
|
|
text_section->FindPortion(kSnapshotTextAsmSymbol);
|
|
ASSERT(isolate_instructions != nullptr);
|
|
|
|
auto* const data_section =
|
|
text_segment_->FindSection(mach_o::SECT_CONST, mach_o::SEG_TEXT);
|
|
auto* const isolate_data =
|
|
data_section == nullptr
|
|
? nullptr
|
|
: data_section->FindPortion(kSnapshotDataAsmSymbol);
|
|
|
|
uint64_t hashes[2];
|
|
COMPILE_ASSERT(sizeof(hashes) == 16); // 128-bit UUID
|
|
hashes[0] = HashPortion(*isolate_instructions);
|
|
hashes[1] = HashPortion(*isolate_data);
|
|
|
|
auto* const uuid_command = new (zone()) MachOUuid(hashes, sizeof(hashes));
|
|
commands_.Add(uuid_command);
|
|
}
|
|
|
|
void MachOHeader::CreateBSS() {
|
|
// No text section means no BSS section.
|
|
auto* const text_section = FindSection(mach_o::SEG_TEXT, mach_o::SECT_TEXT);
|
|
ASSERT(text_section != nullptr);
|
|
|
|
// Not idempotent.
|
|
ASSERT(FindSection(mach_o::SECT_BSS, mach_o::SEG_DATA) == nullptr);
|
|
MachOSegment* data_segment = nullptr;
|
|
if (type_ == SnapshotType::Object) {
|
|
// The "text" segment in a relocatable object is the unnamed segment
|
|
// that contains all sections.
|
|
data_segment = EnsureTextSegment();
|
|
ASSERT(data_segment->HasName(mach_o::SEG_UNNAMED));
|
|
} else {
|
|
// Currently the data segment only contains BSS data, so it
|
|
// shouldn't already exist.
|
|
ASSERT(FindSegment(mach_o::SEG_DATA) == nullptr);
|
|
auto const vm_protection = mach_o::VM_PROT_READ | mach_o::VM_PROT_WRITE;
|
|
data_segment = new (zone()) MachOSegment(zone(), type_, mach_o::SEG_DATA,
|
|
vm_protection, vm_protection);
|
|
commands_.Add(data_segment);
|
|
}
|
|
|
|
auto* const bss_section = new (zone()) MachOSection(
|
|
zone(), mach_o::SECT_BSS, mach_o::SEG_DATA,
|
|
/*alignment=*/compiler::target::kWordSize, mach_o::S_ZEROFILL,
|
|
mach_o::S_NO_ATTRIBUTES, /*has_contents=*/false);
|
|
data_segment->AddContents(bss_section);
|
|
|
|
for (const auto& portion : text_section->portions()) {
|
|
size_t size;
|
|
const char* symbol_name;
|
|
intptr_t label;
|
|
// First determine whether this is the VM's text portion or the isolate's.
|
|
if (strcmp(portion.symbol_name, kSnapshotTextAsmSymbol) == 0) {
|
|
size = BSS::kIsolateGroupEntryCount * compiler::target::kWordSize;
|
|
symbol_name = kSnapshotBssAsmSymbol;
|
|
label = SharedObjectWriter::kIsolateBssLabel;
|
|
} else {
|
|
// Not VM or isolate text.
|
|
UNREACHABLE();
|
|
}
|
|
|
|
// For the BSS section, we add the section symbols as local symbols in the
|
|
// static symbol table, as these addresses are only used for relocation.
|
|
// (This matches the behavior in the assembly output.)
|
|
auto* symbols = new (zone_) SharedObjectWriter::SymbolDataArray(zone_, 1);
|
|
symbols->Add({symbol_name, SharedObjectWriter::SymbolData::Type::Section, 0,
|
|
size, label});
|
|
bss_section->AddPortion(/*bytes=*/nullptr, size, /*relocations=*/nullptr,
|
|
symbols);
|
|
}
|
|
}
|
|
|
|
#if defined(DART_TARGET_OS_MACOS) && defined(TARGET_ARCH_ARM64)
|
|
void MachOHeader::GenerateCompactUnwindingInformation(
|
|
DwarfSharedObjectStream& stream,
|
|
const GrowableArray<Dwarf::FrameDescriptionEntry>& fdes) {
|
|
// Each instructions image starts with the Image header and the
|
|
// InstructionsSection header.
|
|
const intptr_t header_size =
|
|
Image::kHeaderSize + compiler::target::InstructionsSection::HeaderSize();
|
|
|
|
if (type_ == SnapshotType::Object) {
|
|
// In relocatable objects, the compact unwind information is written
|
|
// differently. In this case, it's just a flat table with entries of
|
|
// the following format:
|
|
// start (word-sized)
|
|
// length (32 bits)
|
|
// encoding (32 bits)
|
|
// personality-function (word-sized, 0 if none)
|
|
// ldsa (word-sized, 0 if none)
|
|
for (intptr_t i = 0, n = fdes.length(); i < n; i++) {
|
|
const auto& fde = fdes[i];
|
|
// The payload of the InstructionsSection.
|
|
stream.OffsetFromSymbol(fde.label, header_size);
|
|
stream.u4(fde.size);
|
|
stream.u4(mach_o::UNWIND_INFO_ENCODING_ARM64_MODE_FRAME);
|
|
#if defined(TARGET_ARCH_IS_32_BIT)
|
|
stream.u4(0); // Personality function
|
|
stream.u4(0); // LDSA
|
|
#else
|
|
stream.u8(0); // Personality function
|
|
stream.u8(0); // LDSA
|
|
#endif
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Since we currently generate only regular second level pages, there's
|
|
// no need for common encodings as those are only used by compressed
|
|
// second level pages.
|
|
const intptr_t common_encodings_offset = sizeof(mach_o::unwind_info_header);
|
|
GrowableArray<uint32_t> common_encodings(zone(), 0);
|
|
|
|
const intptr_t personalities_offset =
|
|
common_encodings_offset + common_encodings.length() * kInt32Size;
|
|
GrowableArray<uint32_t> personalities(zone(), 0);
|
|
|
|
// For N FDEs, we generate 2N entries:
|
|
// * One at the start of the text section with the none encoding.
|
|
// * One at the start of each FDE's InstructionsSection payload with
|
|
// the frame encoding.
|
|
// * For all but the last FDE, one at the end of the InstructionsSection
|
|
// payload with the none encoding.
|
|
// No entry is needed for the end of the last FDE, since it is
|
|
// already recorded as the end of the instructions in the first
|
|
// page index sentinel entry.
|
|
const intptr_t second_level_page_entry_count = 2 * fdes.length();
|
|
const bool second_level_pages_count =
|
|
(second_level_page_entry_count +
|
|
(mach_o::UNWIND_INFO_REGULAR_SECOND_LEVEL_PAGE_MAX_ENTRIES - 1)) /
|
|
mach_o::UNWIND_INFO_REGULAR_SECOND_LEVEL_PAGE_MAX_ENTRIES;
|
|
|
|
const intptr_t first_level_page_indices_offset =
|
|
personalities_offset + personalities.length() * kInt32Size;
|
|
// There is one first level page index per second level page, plus an
|
|
// additional first level page index that serves as as a sentinel and
|
|
// contains the ending offset of the LSDA entries.
|
|
const intptr_t first_level_page_indices_count = second_level_pages_count + 1;
|
|
|
|
// Align the LSDA indices to the target word size, as the first level page
|
|
// indices are 12 bytes long and so may not end on a word boundary
|
|
// on 64-bit systems.
|
|
const intptr_t lsda_indices_offset =
|
|
Utils::RoundUp(first_level_page_indices_offset +
|
|
first_level_page_indices_count *
|
|
sizeof(mach_o::unwind_info_first_level_page_index),
|
|
compiler::target::kWordSize);
|
|
GrowableArray<mach_o::unwind_info_lsda_index> lsda_indices(zone(), 0);
|
|
|
|
const intptr_t second_level_pages_offset =
|
|
lsda_indices_offset +
|
|
lsda_indices.length() * sizeof(mach_o::unwind_info_lsda_index);
|
|
// We should only generate at most 2 FDEs and thus 4 entries, so there
|
|
// should only be one second level page that, if placed right after
|
|
// the other content, is wholly contained in a 4 * KB page.
|
|
ASSERT_EQUAL(1, second_level_pages_count);
|
|
const intptr_t second_level_pages_size =
|
|
mach_o::UnwindInfoRegularSecondLevelPageSize(
|
|
second_level_page_entry_count);
|
|
const intptr_t unwind_info_size =
|
|
second_level_pages_offset + second_level_pages_size;
|
|
ASSERT(static_cast<size_t>(unwind_info_size) <=
|
|
mach_o::UNWIND_INFO_SECOND_LEVEL_PAGE_MAX_SIZE);
|
|
|
|
stream.u4(mach_o::UNWIND_INFO_VERSION);
|
|
stream.u4(common_encodings_offset);
|
|
stream.u4(common_encodings.length());
|
|
stream.u4(personalities_offset);
|
|
stream.u4(personalities.length());
|
|
stream.u4(first_level_page_indices_offset);
|
|
stream.u4(first_level_page_indices_count);
|
|
|
|
ASSERT_EQUAL(common_encodings_offset, stream.Position());
|
|
for (const auto& encoding : common_encodings) {
|
|
stream.u4(encoding);
|
|
}
|
|
|
|
ASSERT_EQUAL(personalities_offset, stream.Position());
|
|
for (const auto& personality : personalities) {
|
|
stream.u4(personality);
|
|
}
|
|
|
|
ASSERT_EQUAL(first_level_page_indices_offset, stream.Position());
|
|
ASSERT_EQUAL(2, first_level_page_indices_count);
|
|
const auto& first_fde = fdes[0];
|
|
const auto& last_fde = fdes.Last();
|
|
stream.OffsetFromSymbol(first_fde.label, 0, kInt32Size);
|
|
stream.u4(second_level_pages_offset);
|
|
stream.u4(lsda_indices_offset);
|
|
// Sentinel that includes the end of the function space as the offset
|
|
// and has an LSDA index offset at the end of the LSDA index array.
|
|
stream.OffsetFromSymbol(last_fde.label, last_fde.size, kInt32Size);
|
|
stream.u4(0); // No second level page.
|
|
stream.u4(lsda_indices_offset +
|
|
lsda_indices.length() * sizeof(mach_o::unwind_info_lsda_index));
|
|
|
|
stream.Align(compiler::target::kWordSize);
|
|
ASSERT_EQUAL(lsda_indices_offset, stream.Position());
|
|
for (const auto& lsda_index : lsda_indices) {
|
|
stream.u4(lsda_index.function_offset);
|
|
stream.u4(lsda_index.lsda_offset);
|
|
}
|
|
|
|
ASSERT_EQUAL(second_level_pages_offset, stream.Position());
|
|
ASSERT_EQUAL(1, second_level_pages_count);
|
|
stream.u4(mach_o::UNWIND_INFO_REGULAR_SECOND_LEVEL_PAGE);
|
|
stream.u2(sizeof(mach_o::unwind_info_regular_second_level_page_header));
|
|
stream.u2(second_level_page_entry_count);
|
|
// There are no instructions until the first InstructionsSection payload.
|
|
stream.OffsetFromSymbol(fdes[0].label, 0, kInt32Size);
|
|
stream.u4(mach_o::UNWIND_INFO_ENCODING_NONE);
|
|
for (intptr_t i = 0, n = fdes.length(); i < n - 1; i++) {
|
|
const auto& fde = fdes[i];
|
|
// The payload of the InstructionsSection.
|
|
stream.OffsetFromSymbol(fde.label, header_size, kInt32Size);
|
|
stream.u4(mach_o::UNWIND_INFO_ENCODING_ARM64_MODE_FRAME);
|
|
// The padding (if any) between this Image and the next.
|
|
stream.OffsetFromSymbol(fde.label, fde.size, kInt32Size);
|
|
stream.u4(mach_o::UNWIND_INFO_ENCODING_NONE);
|
|
}
|
|
// The payload of the last InstructionsSection.
|
|
stream.OffsetFromSymbol(fdes.Last().label, header_size, kInt32Size);
|
|
stream.u4(mach_o::UNWIND_INFO_ENCODING_ARM64_MODE_FRAME);
|
|
ASSERT_EQUAL(unwind_info_size, stream.Position());
|
|
}
|
|
#endif
|
|
|
|
void MachOHeader::GenerateUnwindingInformation() {
|
|
#if !defined(TARGET_ARCH_IA32)
|
|
// Unwinding information is added to the text segment in Mach-O files
|
|
// (except for relocatable object files, where the __LD segment name is used).
|
|
// Thus, we need the size of the unwinding information even for debugging
|
|
// information, since adding the unwinding information changes the memory size
|
|
// of the initial text segment and thus changes the values for symbols
|
|
// of sections in later segments.
|
|
//
|
|
// However, since the debugging information should never be loaded by
|
|
// the Mach-O loader, we don't actually need to generate the instructions,
|
|
// just use an appropriate zerofill section for it.
|
|
const bool use_zerofill = type_ == SnapshotType::DebugInfo;
|
|
const intptr_t alignment = compiler::target::kWordSize;
|
|
auto create_unwind_section =
|
|
[&](const char* segname, const char* sectname,
|
|
const ZoneWriteStream& stream,
|
|
const SharedObjectWriter::RelocationArray* relocations = nullptr,
|
|
const SharedObjectWriter::SymbolDataArray* symbols =
|
|
nullptr) -> MachOSection* {
|
|
// Not idempotent.
|
|
ASSERT(FindSection(sectname, segname) == nullptr);
|
|
auto* const section = new (zone())
|
|
MachOSection(zone(), sectname, segname, alignment,
|
|
use_zerofill ? mach_o::S_ZEROFILL : mach_o::S_REGULAR,
|
|
mach_o::S_NO_ATTRIBUTES, !use_zerofill);
|
|
section->AddPortion(use_zerofill ? nullptr : stream.buffer(),
|
|
stream.bytes_written(),
|
|
use_zerofill ? nullptr : relocations, symbols);
|
|
return section;
|
|
};
|
|
|
|
ASSERT(text_segment_ != nullptr);
|
|
if (auto* const text_section =
|
|
text_segment_->FindSection(mach_o::SECT_TEXT, mach_o::SEG_TEXT)) {
|
|
// Generate the DWARF FDEs even for MacOS, because the same information
|
|
// is used to create the compact unwinding info.
|
|
GrowableArray<Dwarf::FrameDescriptionEntry> fdes(zone_, 0);
|
|
for (const auto& portion : text_section->portions()) {
|
|
ASSERT(portion.label != 0);
|
|
fdes.Add({portion.label, portion.size});
|
|
}
|
|
|
|
// Even if the unwinding information is not written to the output, it is
|
|
// generated so a zerofill section of the appropriate size can be created.
|
|
ZoneWriteStream stream(zone(), DwarfSharedObjectStream::kInitialBufferSize);
|
|
DwarfSharedObjectStream dwarf_stream(zone(), &stream);
|
|
|
|
SharedObjectWriter::SymbolDataArray* symbols = nullptr;
|
|
#if defined(DART_TARGET_OS_MACOS) && defined(TARGET_ARCH_ARM64)
|
|
GenerateCompactUnwindingInformation(dwarf_stream, fdes);
|
|
auto* const sectname = type_ == SnapshotType::Object
|
|
? mach_o::SECT_COMPACT_UNWIND
|
|
: mach_o::SECT_UNWIND_INFO;
|
|
#else
|
|
Dwarf::WriteCallFrameInformationRecords(&dwarf_stream, fdes);
|
|
auto* const sectname = mach_o::SECT_EH_FRAME;
|
|
if (type_ == SnapshotType::Object) {
|
|
// To add appropriate relocations for the EH_FRAME section, a local symbol
|
|
// must be added since this section includes relocations with
|
|
// kSelfRelative source labels.
|
|
const size_t size = stream.bytes_written();
|
|
symbols = new (zone_) SharedObjectWriter::SymbolDataArray(zone_, 1);
|
|
symbols->Add({"_kDartMachOEhFrameSection",
|
|
SharedObjectWriter::SymbolData::Type::Section, 0, size,
|
|
SharedObjectWriter::kMachOEhFrameLabel});
|
|
}
|
|
#endif
|
|
|
|
auto* const segname =
|
|
type_ == SnapshotType::Object ? mach_o::SEG_LD : mach_o::SEG_TEXT;
|
|
auto* const section = create_unwind_section(
|
|
segname, sectname, stream, dwarf_stream.relocations(), symbols);
|
|
text_segment_->AddContents(section);
|
|
}
|
|
|
|
#if defined(UNWINDING_RECORDS_WINDOWS_PRECOMPILER)
|
|
// Append Windows unwinding instructions as a __unwind_info section at
|
|
// the end of any executable segments.
|
|
//
|
|
// Don't do this for relocatable objects, because those can't be loaded
|
|
// by the non-native loader and there's no way to link them into a
|
|
// program since Mach-O is not a supported object type on Windows anyway.
|
|
if (type_ != SnapshotType::Object) {
|
|
for (auto* const command : commands_) {
|
|
if (auto* const segment = command->AsMachOSegment()) {
|
|
if (segment->IsExecutable()) {
|
|
// Only more zerofill sections can come after zerofill sections, and
|
|
// the unwinding instructions cover the entire executable segment up
|
|
// to the unwinding instructions including zerofill sections.
|
|
ASSERT(use_zerofill || !segment->HasZerofillSections());
|
|
const intptr_t records_size = UnwindingRecordsPlatform::SizeInBytes();
|
|
ZoneWriteStream stream(zone(), /*initial_size=*/records_size);
|
|
uint8_t* unwinding_instructions =
|
|
zone()->Alloc<uint8_t>(records_size);
|
|
const intptr_t section_start =
|
|
Utils::RoundUp(segment->UnpaddedMemorySize(), alignment);
|
|
stream.WriteBytes(UnwindingRecords::GenerateRecordsInto(
|
|
section_start, unwinding_instructions),
|
|
records_size);
|
|
ASSERT_EQUAL(records_size, stream.Position());
|
|
auto* const section = create_unwind_section(
|
|
segment->name(), mach_o::SECT_UNWIND_INFO, stream);
|
|
segment->AddContents(section);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif // defined(DART_TARGET_OS_WINDOWS) && defined(TARGET_ARCH_IS_64_BIT)
|
|
#endif // !defined(TARGET_ARCH_IA32)
|
|
}
|
|
|
|
void MachOHeader::GenerateMiscellaneousCommands() {
|
|
if (type_ == SnapshotType::Snapshot) {
|
|
// Not idempotent;
|
|
ASSERT(!HasCommand(MachOIdDylib::kCommandCode));
|
|
commands_.Add(new (zone_) MachOIdDylib(identifier_));
|
|
if (FLAG_macho_encryptable) {
|
|
ASSERT(!HasCommand(MachOEncryptionInfo::kCommandCode));
|
|
commands_.Add(new (zone_) MachOEncryptionInfo());
|
|
}
|
|
#if defined(DART_TARGET_OS_MACOS) || defined(DART_TARGET_OS_MACOS_IOS)
|
|
ASSERT(!HasCommand(MachOLoadDylib::kCommandCode));
|
|
commands_.Add(MachOLoadDylib::CreateLoadSystemDylib(zone_));
|
|
ASSERT(!HasCommand(MachORunPath::kCommandCode));
|
|
if (FLAG_macho_rpath != nullptr) {
|
|
const char* current = FLAG_macho_rpath;
|
|
for (const char* next = current;; next += 1) {
|
|
if (*next == ',' || *next == '\0') {
|
|
commands_.Add(new (zone_) MachORunPath(current, next - current));
|
|
if (*next == '\0') break;
|
|
current = next + 1;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
#if defined(DART_TARGET_OS_MACOS) || defined(DART_TARGET_OS_MACOS_IOS)
|
|
if (type_ == SnapshotType::Snapshot || type_ == SnapshotType::Object) {
|
|
ASSERT(!HasCommand(MachOBuildVersion::kCommandCode));
|
|
commands_.Add(new (zone_) MachOBuildVersion());
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void MachOHeader::InitializeSymbolTables() {
|
|
// Not idempotent.
|
|
ASSERT_EQUAL(full_symtab_.num_symbols(), 0);
|
|
ASSERT(!HasCommand(MachOSymbolTable::kCommandCode));
|
|
|
|
// Grab all the sections in order and set their current index.
|
|
GrowableArray<MachOSection*> sections(zone_, 0);
|
|
intptr_t section_index = 1; // 1-based.
|
|
for (auto* const command : commands_) {
|
|
// Should be run before ComputeOffsets.
|
|
ASSERT(!command->HasContents() || !command->file_offset_is_set());
|
|
if (auto* const s = command->AsMachOSegment()) {
|
|
for (auto* const c : s->contents()) {
|
|
if (auto* const section = c->AsMachOSection()) {
|
|
section->set_index(section_index++);
|
|
sections.Add(section);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// This symbol table is for the MachOWriter's internal use. All symbols
|
|
// should be added to it so the writer can resolve relocations.
|
|
full_symtab_.Initialize(type_, path_, sections, /*is_stripped=*/false);
|
|
auto* table = &full_symtab_;
|
|
if (is_stripped_) {
|
|
// Create a separate symbol table that is actually written to the output.
|
|
// This one will only contain what's needed for the dynamic symbol table.
|
|
auto* const table = new (zone())
|
|
MachOSymbolTable(zone(), /*in_segment=*/type_ != SnapshotType::Object);
|
|
table->Initialize(type_, path_, sections, is_stripped_);
|
|
}
|
|
commands_.Add(table);
|
|
|
|
// For non-debugging information, include a dynamic symbol table as well.
|
|
if (type_ != SnapshotType::DebugInfo) {
|
|
auto* const dynamic_symtab = new (zone()) MachODynamicSymbolTable(
|
|
*table, /*in_segment=*/type_ != SnapshotType::Object);
|
|
commands_.Add(dynamic_symtab);
|
|
}
|
|
}
|
|
|
|
void MachOHeader::FinalizeDwarfSections() {
|
|
if (dwarf_ == nullptr) return;
|
|
|
|
if (has_separate_object_) {
|
|
// If there is an associated relocatable object, do not emit the DWARF
|
|
// sections; they'll be emitted in the relocatable object instead.
|
|
ASSERT(type_ == SnapshotType::Snapshot);
|
|
return;
|
|
}
|
|
|
|
// Currently we only output DWARF information involving code.
|
|
#if defined(DEBUG)
|
|
ASSERT(text_segment_ != nullptr);
|
|
ASSERT(text_segment_->FindSection(mach_o::SECT_TEXT, mach_o::SEG_TEXT) !=
|
|
nullptr);
|
|
#endif
|
|
|
|
MachOSegment* dwarf_segment = nullptr;
|
|
if (type_ == SnapshotType::Object) {
|
|
// All sections are put into the unnamed segment.
|
|
dwarf_segment = text_segment_;
|
|
ASSERT(dwarf_segment->HasName(mach_o::SEG_UNNAMED));
|
|
} else {
|
|
// Create the DWARF segment, which should not already exist.
|
|
ASSERT(FindSegment(mach_o::SEG_DWARF) == nullptr);
|
|
auto const init_vm_protection =
|
|
mach_o::VM_PROT_READ | mach_o::VM_PROT_WRITE;
|
|
auto const max_vm_protection = init_vm_protection | mach_o::VM_PROT_EXECUTE;
|
|
dwarf_segment =
|
|
new (zone()) MachOSegment(zone(), type_, mach_o::SEG_DWARF,
|
|
init_vm_protection, max_vm_protection);
|
|
commands_.Add(dwarf_segment);
|
|
}
|
|
|
|
const intptr_t alignment = 1; // No extra padding.
|
|
auto add_debug = [&](const char* name,
|
|
const DwarfSharedObjectStream& stream) {
|
|
ASSERT(!dwarf_segment->FindSection(name, mach_o::SEG_DWARF));
|
|
auto* const section =
|
|
new (zone()) MachOSection(zone(), name, mach_o::SEG_DWARF, alignment,
|
|
mach_o::S_REGULAR, mach_o::S_ATTR_DEBUG);
|
|
section->AddPortion(stream.buffer(), stream.bytes_written(),
|
|
stream.relocations());
|
|
dwarf_segment->AddContents(section);
|
|
};
|
|
|
|
{
|
|
ZoneWriteStream stream(zone(), DwarfSharedObjectStream::kInitialBufferSize);
|
|
DwarfSharedObjectStream dwarf_stream(zone_, &stream);
|
|
dwarf_->WriteAbbreviations(&dwarf_stream);
|
|
add_debug(mach_o::SECT_DEBUG_ABBREV, dwarf_stream);
|
|
}
|
|
|
|
{
|
|
ZoneWriteStream stream(zone(), DwarfSharedObjectStream::kInitialBufferSize);
|
|
DwarfSharedObjectStream dwarf_stream(zone_, &stream);
|
|
dwarf_->WriteDebugInfo(&dwarf_stream);
|
|
add_debug(mach_o::SECT_DEBUG_INFO, dwarf_stream);
|
|
}
|
|
|
|
{
|
|
ZoneWriteStream stream(zone(), DwarfSharedObjectStream::kInitialBufferSize);
|
|
DwarfSharedObjectStream dwarf_stream(zone_, &stream);
|
|
dwarf_->WriteLineNumberProgram(&dwarf_stream);
|
|
add_debug(mach_o::SECT_DEBUG_LINE, dwarf_stream);
|
|
}
|
|
}
|
|
|
|
void MachOHeader::FinalizeCommands() {
|
|
// Not idempotent.
|
|
ASSERT(FindSegment(mach_o::SEG_LINKEDIT) == nullptr);
|
|
ASSERT(!HasCommand(MachOCodeSignature::kCommandCode));
|
|
|
|
intptr_t num_commands = commands_.length();
|
|
// We shouldn't be writing empty Mach-O snapshots.
|
|
ASSERT(num_commands != 0);
|
|
GrowableArray<MachOCommand*> reordered_commands(zone_, num_commands);
|
|
|
|
// Now do a single pass over the commands, sorting them into bins based on
|
|
// the desired final ordering and also calculating a map from old section
|
|
// indices in the old order to new section indices in the new order.
|
|
|
|
// First, any commands that are only part of the header.
|
|
GrowableArray<MachOCommand*> header_only_commands(zone_, 0);
|
|
|
|
// Ensure the text segment is the initial segment. This means the
|
|
// text segment contains the header in its file contents/memory space.
|
|
MachOSegment* text_segment = text_segment_;
|
|
// We should be writing instructions and/or const data.
|
|
ASSERT(text_segment != nullptr);
|
|
|
|
// Then all segments that have defined symbols. These segments
|
|
// are present in both snapshots and separate debugging information,
|
|
// and the symbols defined in these sections should have consistent
|
|
// relocated memory addresses in both.
|
|
GrowableArray<MachOSegment*> symbol_segments(zone_, 0);
|
|
|
|
// Then all other segments added prior to calling this function.
|
|
// These need to be before the linkedit segment, which is created
|
|
// below, so that they are also protected by the code signature
|
|
// (if there is one).
|
|
GrowableArray<MachOSegment*> other_segments(zone_, 0);
|
|
|
|
// Next comes any non-segment load commands that have allocated content
|
|
// outside of the header like the symbol table. For relocatable objects,
|
|
// the contents of these sections are written after the unnamed segment,
|
|
// otherwise a linkedit segment is created later to contain the non-header
|
|
// contents of these commands.
|
|
GrowableArray<MachOCommand*> linkedit_commands(zone_, 0);
|
|
for (auto* const command : commands_) {
|
|
// Check that we're not reordering after offsets have been computed.
|
|
ASSERT(!command->HasContents() || !command->file_offset_is_set());
|
|
if (auto* const s = command->AsMachOSegment()) {
|
|
if (s->HasName(mach_o::SEG_TEXT) || s->HasName(mach_o::SEG_UNNAMED)) {
|
|
ASSERT_EQUAL(type_ == SnapshotType::Object,
|
|
s->HasName(mach_o::SEG_UNNAMED));
|
|
ASSERT(text_segment == s);
|
|
} else if (s->ContainsSymbols()) {
|
|
symbol_segments.Add(s);
|
|
} else {
|
|
other_segments.Add(s);
|
|
}
|
|
} else if (type_ == SnapshotType::Object || command->HasContents()) {
|
|
// Stick every non-segment into linkedit_commands so that the segment
|
|
// load command is first in a relocatable object.
|
|
linkedit_commands.Add(command);
|
|
} else {
|
|
header_only_commands.Add(command);
|
|
}
|
|
}
|
|
|
|
// We should always have a symbol table, even in stripped files where
|
|
// it only contains global exported symbols, which means there should
|
|
// be a linkedit segment.
|
|
ASSERT(!linkedit_commands.is_empty());
|
|
MachOSegment* linkedit_segment = nullptr;
|
|
if (type_ != SnapshotType::Object) {
|
|
linkedit_segment =
|
|
new (zone_) MachOSegment(zone_, type_, mach_o::SEG_LINKEDIT);
|
|
num_commands += 1;
|
|
for (auto* const c : linkedit_commands) {
|
|
linkedit_segment->AddContents(c);
|
|
}
|
|
if (type_ == SnapshotType::Snapshot && FLAG_macho_linker_signature) {
|
|
// Also include an embedded ad-hoc linker signed code signature as the
|
|
// last contents of the linkedit segment (which is the last segment).
|
|
auto* const signature = new (zone_) MachOCodeSignature(identifier_);
|
|
linkedit_segment->AddContents(signature);
|
|
linkedit_commands.Add(signature);
|
|
num_commands += 1;
|
|
}
|
|
}
|
|
|
|
GrowableArray<MachOSegment*> segments(
|
|
zone_, symbol_segments.length() + other_segments.length() + 2);
|
|
// Put the text, data, and linkedit segments in the expected ordering.
|
|
segments.Add(text_segment);
|
|
segments.AddArray(symbol_segments);
|
|
segments.AddArray(other_segments);
|
|
if (type_ != SnapshotType::Object) {
|
|
segments.Add(linkedit_segment);
|
|
}
|
|
|
|
if (type_ != SnapshotType::Object) {
|
|
// The initial segment in the file should have the header as its initial
|
|
// contents. Since the header is not a section, this won't change the
|
|
// section numbering.
|
|
segments[0]->AddContents(this);
|
|
}
|
|
|
|
// Now populate reordered_commands.
|
|
reordered_commands.AddArray(header_only_commands);
|
|
|
|
// While adding segments, also re-index sections.
|
|
intptr_t current_section_index = 1; // 1-based.
|
|
for (auto* const segment : segments) {
|
|
reordered_commands.Add(segment);
|
|
for (auto* const c : segment->contents()) {
|
|
if (auto* const s = c->AsMachOSection()) {
|
|
ASSERT(current_section_index != mach_o::NO_SECT);
|
|
s->set_index(current_section_index++);
|
|
}
|
|
}
|
|
}
|
|
reordered_commands.AddArray(linkedit_commands);
|
|
|
|
// All sections should have been accounted for in the loops above as well as
|
|
// the new linkedit segment (and, if applicable, the code signature).
|
|
ASSERT_EQUAL(reordered_commands.length(), num_commands);
|
|
// Replace the content of commands_ with the reordered commands.
|
|
commands_.Clear();
|
|
commands_.AddArray(reordered_commands);
|
|
}
|
|
|
|
struct ContentOffsetsVisitor : public MachOContents::Visitor {
|
|
explicit ContentOffsetsVisitor(Zone* zone) {}
|
|
|
|
void Default(MachOContents* contents) {
|
|
ASSERT_EQUAL(contents->IsMachOHeader(), file_offset == 0);
|
|
// This can't be strictly equal, because the header is not in a segment
|
|
// in relocatable objects and thus is not allocated, so the first
|
|
// allocated contents (a section) will have a memory offset of 0.
|
|
ASSERT(!contents->IsMachOHeader() || memory_address == 0);
|
|
// Increment the file and memory offsets by the appropriate amounts.
|
|
if (contents->HasContents()) {
|
|
file_offset = Utils::RoundUp(file_offset, contents->Alignment());
|
|
contents->set_file_offset(file_offset);
|
|
file_offset += contents->SelfFileSize();
|
|
}
|
|
if (contents->IsAllocated()) {
|
|
memory_address = Utils::RoundUp(memory_address, contents->Alignment());
|
|
contents->set_memory_address(memory_address);
|
|
memory_address += contents->SelfMemorySize();
|
|
}
|
|
contents->VisitChildren(this);
|
|
if (contents->HasContents()) {
|
|
ASSERT_EQUAL(file_offset, contents->file_offset() + contents->FileSize());
|
|
}
|
|
if (contents->IsAllocated()) {
|
|
ASSERT_EQUAL(memory_address,
|
|
contents->memory_address() + contents->MemorySize());
|
|
}
|
|
}
|
|
|
|
void VisitMachOSegment(MachOSegment* segment) {
|
|
ASSERT_EQUAL(segment->IsInitial(), file_offset == 0);
|
|
ASSERT_EQUAL(segment->IsInitial() || segment->HasName(mach_o::SEG_UNNAMED),
|
|
memory_address == 0);
|
|
// Segments are always allocated and we set the file offset even
|
|
// when the segment doesn't actually write any contents.
|
|
file_offset = Utils::RoundUp(file_offset, segment->Alignment());
|
|
segment->set_file_offset(file_offset);
|
|
file_offset += segment->SelfFileSize();
|
|
memory_address = Utils::RoundUp(memory_address, segment->Alignment());
|
|
segment->set_memory_address(memory_address);
|
|
memory_address += segment->SelfMemorySize();
|
|
segment->VisitChildren(this);
|
|
if (segment->PadFileSizeToAlignment()) {
|
|
file_offset = Utils::RoundUp(file_offset, segment->Alignment());
|
|
}
|
|
memory_address = Utils::RoundUp(memory_address, segment->Alignment());
|
|
ASSERT_EQUAL(file_offset, segment->file_offset() + segment->FileSize());
|
|
ASSERT_EQUAL(memory_address,
|
|
segment->memory_address() + segment->MemorySize());
|
|
}
|
|
|
|
intptr_t file_offset = 0;
|
|
intptr_t memory_address = 0;
|
|
|
|
private:
|
|
DISALLOW_COPY_AND_ASSIGN(ContentOffsetsVisitor);
|
|
};
|
|
|
|
class RelocationsConverter : public MachOContents::Visitor {
|
|
using SnapshotType = SharedObjectWriter::Type;
|
|
using Relocation = SharedObjectWriter::Relocation;
|
|
|
|
public:
|
|
explicit RelocationsConverter(Zone* zone,
|
|
const MachOHeader& header,
|
|
intptr_t start)
|
|
: zone_(zone),
|
|
output_is_relocatable_object_(header.type() == SnapshotType::Object),
|
|
symbol_table_(*ASSERT_NOTNULL(header.IncludedSymbolTable())),
|
|
file_offset_(Utils::RoundUp(start, compiler::target::kWordSize)),
|
|
portion_and_section_by_label_(zone) {
|
|
for (auto* const command : header.commands()) {
|
|
if (auto* const segment = command->AsMachOSegment()) {
|
|
for (auto* const c : segment->contents()) {
|
|
if (auto* const s = c->AsMachOSection()) {
|
|
for (const auto& p : s->portions()) {
|
|
portion_and_section_by_label_.Insert({p.label, {s, &p}});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Default(MachOContents* contents) override {}
|
|
|
|
void VisitMachOSegment(MachOSegment* segment) override {
|
|
segment->VisitChildren(this);
|
|
}
|
|
|
|
void VisitMachOSection(MachOSection* section) override {
|
|
current_relocations_ = nullptr;
|
|
current_relocation_addends_ = nullptr;
|
|
if (output_is_relocatable_object_) {
|
|
ASSERT(section->file_offset_is_set());
|
|
current_section_ = section;
|
|
for (const auto& p : section->portions()) {
|
|
if (p.relocations == nullptr) continue;
|
|
if (p.symbols != nullptr && !p.symbols->is_empty()) {
|
|
// Local symbols are sorted in order of offset into the portion.
|
|
starting_index_for_self_ =
|
|
symbol_table_.IndexForLabel(p.symbols->At(0).label);
|
|
}
|
|
current_portion_ = &p;
|
|
for (const auto& reloc : *p.relocations) {
|
|
ConvertRelocation(reloc);
|
|
}
|
|
starting_index_for_self_ = -1;
|
|
current_portion_ = nullptr;
|
|
}
|
|
current_section_ = nullptr;
|
|
}
|
|
section->set_relocations(current_relocations_);
|
|
section->set_relocation_addends(current_relocation_addends_);
|
|
section->set_relocations_file_offset(
|
|
current_relocations_ != nullptr ? file_offset_ : 0);
|
|
file_offset_ +=
|
|
section->num_relocations() * sizeof(mach_o::relocation_info);
|
|
}
|
|
|
|
private:
|
|
static uint32_t RelocationSize(intptr_t size) {
|
|
switch (size) {
|
|
case 1:
|
|
return mach_o::RELOC_SIZE_BYTE;
|
|
case 2:
|
|
return mach_o::RELOC_SIZE_2BYTES;
|
|
case 4:
|
|
return mach_o::RELOC_SIZE_4BYTES;
|
|
case 8:
|
|
return mach_o::RELOC_SIZE_8BYTES;
|
|
default:
|
|
FATAL("Unexpected relocation size %" Pd "", size);
|
|
return mach_o::RELOC_SIZE_BYTE;
|
|
}
|
|
}
|
|
|
|
// Finds the closest symbol to the offset in the given section portion.
|
|
// Starts the search of local symbols in the symbol table from starting_index
|
|
// and updates starting_index if the closest symbol is a local symbol with an
|
|
// appropriate place to start the next search for any portion offsets after
|
|
// the current one.
|
|
intptr_t FindClosestSymbolIndexTo(intptr_t portion_offset,
|
|
const MachOSection* section,
|
|
const MachOSection::Portion* portion,
|
|
intptr_t& starting_index) {
|
|
const uword address =
|
|
section->memory_address() + portion->offset + portion_offset;
|
|
MachOSymbolTable::Symbol* current = nullptr;
|
|
if (starting_index >= 0) {
|
|
current = &symbol_table_.symbols()[starting_index];
|
|
if (current->value() == address) {
|
|
return starting_index;
|
|
} else if (current->value() < address) {
|
|
// Since index is the index of a local symbol, we're guaranteed
|
|
// that there's always at least one more symbol in the symbol table,
|
|
// as the global symbols come afterwards.
|
|
auto* next_symbol = &symbol_table_.symbols()[starting_index + 1];
|
|
// Search until we run out of local symbols for this section.
|
|
while (next_symbol->section_index() == section->index()) {
|
|
// Stop if the current symbol is closer than the next.
|
|
if (Utils::Abs(next_symbol->value() - address) >=
|
|
Utils::Abs(address - current->value())) {
|
|
break;
|
|
}
|
|
++starting_index;
|
|
current = next_symbol;
|
|
next_symbol = &symbol_table_.symbols()[starting_index + 1];
|
|
}
|
|
return starting_index;
|
|
}
|
|
// Fall through to see if the closest global symbol preceding
|
|
// this address is closer.
|
|
}
|
|
intptr_t label = portion->label;
|
|
if (label == 0) {
|
|
// Search for a global symbol label in the portions preceding this one.
|
|
for (const auto& p : section->portions()) {
|
|
if (portion == &p) break;
|
|
if (p.label != 0) {
|
|
label = p.label;
|
|
}
|
|
}
|
|
}
|
|
if (current != nullptr) {
|
|
// Check to see if the found global symbol (if any) is closer than the
|
|
// local symbol following this address.
|
|
intptr_t global_index = symbol_table_.IndexForLabel(label);
|
|
if (global_index >= 0) {
|
|
const auto& global_symbol = symbol_table_.symbols()[global_index];
|
|
if (Utils::Abs(address - global_symbol.value()) <=
|
|
Utils::Abs(current->value() - address)) {
|
|
return global_index;
|
|
}
|
|
}
|
|
return starting_index;
|
|
}
|
|
// IndexForLabel will return a negative value for label == 0.
|
|
return symbol_table_.IndexForLabel(label);
|
|
}
|
|
|
|
MachORelocationsArray* EnsureCurrentRelocations() {
|
|
if (current_relocations_ == nullptr) {
|
|
current_relocations_ = new (zone_) MachORelocationsArray(zone_, 0);
|
|
}
|
|
return current_relocations_;
|
|
}
|
|
|
|
MachORelocationAddendsArray* EnsureCurrentRelocationAddends() {
|
|
if (current_relocation_addends_ == nullptr) {
|
|
current_relocation_addends_ =
|
|
new (zone_) MachORelocationAddendsArray(zone_, 0);
|
|
}
|
|
return current_relocation_addends_;
|
|
}
|
|
|
|
void ConvertRelocation(const Relocation& reloc) {
|
|
// In the Dart VM, we turn relocations into up to three different parts
|
|
// in a Mach-O relocatable object:
|
|
//
|
|
// - A SUBTRACTOR relocation entry for [reloc.source_label].
|
|
// - An UNSIGNED relocation entry for [reloc.target_label].
|
|
// - An addend stored at [reloc.section_offset] in the section contents.
|
|
//
|
|
// If the source and target labels are the same, then there are no
|
|
// relocation entries added and the addend is simply:
|
|
// [reloc.target_offset] - [reloc.source_offset]
|
|
//
|
|
// If there are distinct source and target labels, then a SUBTRACTOR
|
|
// relocation entry for [reloc.source_label] is emitted followed by the
|
|
// UNSIGNED relocation entry for [reloc.target_label]. Both relocation
|
|
// entries are based on symbols. The addend, like the previous case, is:
|
|
// [reloc.target_offset] - [reloc.source_offset]
|
|
//
|
|
// If [reloc.source_label] is kSnapshotRelative, then only an UNSIGNED
|
|
// relocation based on the section is emitted. Since a section-based
|
|
// relocation entry is used, the addend differs from the other two cases:
|
|
// [current_section_.memory_address()] + [reloc.section_offset] +
|
|
// [reloc.target_offset] - [reloc.source_offset]
|
|
// That is, the virtual address of the relocation is added to the addend.
|
|
if (!Utils::IsInt(kBitsPerInt32,
|
|
current_portion_->offset + reloc.section_offset)) {
|
|
FATAL("Offset into section for relocation is not a 32-bit integer.");
|
|
}
|
|
int32_t section_offset = current_portion_->offset + reloc.section_offset;
|
|
const intptr_t address =
|
|
current_section_->memory_address() + section_offset;
|
|
if (reloc.target_label == SharedObjectWriter::kBuildIdLabel) {
|
|
ASSERT_EQUAL(reloc.target_offset, 0);
|
|
ASSERT_EQUAL(reloc.source_offset, 0);
|
|
ASSERT_EQUAL(reloc.size_in_bytes, compiler::target::kWordSize);
|
|
// Build IDs are UUID load commands in Mach-O and so have no
|
|
// associated symbol to use for relocations.
|
|
EnsureCurrentRelocationAddends()->Add(Image::kNoBuildId);
|
|
return;
|
|
}
|
|
intptr_t addend = reloc.target_offset - reloc.source_offset;
|
|
// If there is an emitted subtrahend (the source), emit it before
|
|
// the minuend (the target).
|
|
bool emitted_subtrahend = false;
|
|
if (reloc.source_label == reloc.target_label) {
|
|
// The relocation can be computed eagerly as the source and target
|
|
// refer to the same object.
|
|
} else if (reloc.source_label == Relocation::kSnapshotRelative) {
|
|
ASSERT_EQUAL(reloc.source_offset, 0);
|
|
ASSERT_EQUAL(reloc.size_in_bytes, compiler::target::kWordSize);
|
|
if (auto* const kv =
|
|
portion_and_section_by_label_.Lookup(reloc.target_label)) {
|
|
const auto [section, portion] = kv->value;
|
|
if (section == current_section_) {
|
|
ASSERT(section->HasName(mach_o::SECT_TEXT) &&
|
|
section->HasSegname(mach_o::SEG_TEXT));
|
|
// This is considered an illegal text relocation by clang, so omit
|
|
// this relocation.
|
|
EnsureCurrentRelocationAddends()->Add(Image::kNoRelocatedAddress);
|
|
return;
|
|
}
|
|
}
|
|
} else {
|
|
// The subtrahend _must_ be a symbol.
|
|
intptr_t index = -1;
|
|
if (reloc.source_label == Relocation::kSelfRelative) {
|
|
index = FindClosestSymbolIndexTo(reloc.section_offset, current_section_,
|
|
current_portion_,
|
|
starting_index_for_self_);
|
|
if (index < 0) {
|
|
FATAL("Cannot find any symbol in section %" Pd "",
|
|
current_section_->index());
|
|
}
|
|
const auto& closest_symbol = symbol_table_.symbols()[index];
|
|
// Adjust the addend by subtracting the offset from the found symbol.
|
|
addend -= address - closest_symbol.value();
|
|
} else {
|
|
index = symbol_table_.IndexForLabel(reloc.source_label);
|
|
RELEASE_ASSERT(index >= 0);
|
|
}
|
|
if (!Utils::IsUint(mach_o::RELOC_METADATA_INDEX_BITS, index)) {
|
|
FATAL("Symbol index cannot fit into metadata payload: %" Pd "", index);
|
|
}
|
|
uint32_t source_metadata = index | mach_o::RELOC_EXTERN;
|
|
#if defined(TARGET_ARCH_X64)
|
|
source_metadata |= mach_o::RELOC_TYPE_X64_SUBTRACTOR;
|
|
#elif defined(TARGET_ARCH_ARM64)
|
|
source_metadata |= mach_o::RELOC_TYPE_ARM64_SUBTRACTOR;
|
|
#else
|
|
// Relocatable objects aren't handled for this architecture.
|
|
UNREACHABLE();
|
|
#endif
|
|
source_metadata |= RelocationSize(reloc.size_in_bytes);
|
|
EnsureCurrentRelocations()->Add({section_offset, source_metadata});
|
|
emitted_subtrahend = true;
|
|
}
|
|
// Now calculate the relocation entry for the base or minuend (target).
|
|
// If there is no subtrahend, the base is emitted as a relocation using
|
|
// a section index. If the target and source are the same, then no entries
|
|
// are emitted and the relocatable value is computed eagerly and stored as
|
|
// the addend.
|
|
//
|
|
// Note that the base addend for section-based relocations is the virtual
|
|
// address of the relocation, which is added here after determining which
|
|
// kind of relocation to use.
|
|
//
|
|
// For symbol-based relocations, the base addend is 0.
|
|
if (reloc.target_label != reloc.source_label) {
|
|
uint32_t target_metadata = 0;
|
|
intptr_t index = symbol_table_.IndexForLabel(reloc.target_label);
|
|
if (index >= 0) {
|
|
if (emitted_subtrahend) {
|
|
// Both subtrahend and minuend must be emitted as symbols.
|
|
target_metadata |= mach_o::RELOC_EXTERN;
|
|
} else {
|
|
const auto& symbol = symbol_table_.symbols()[index];
|
|
index = symbol.section_index();
|
|
addend += symbol.value();
|
|
}
|
|
} else if (reloc.target_label == Relocation::kSelfRelative) {
|
|
if (emitted_subtrahend) {
|
|
index = FindClosestSymbolIndexTo(reloc.section_offset,
|
|
current_section_, current_portion_,
|
|
starting_index_for_self_);
|
|
if (index < 0) {
|
|
FATAL("Cannot find any symbol in section %" Pd "",
|
|
current_section_->index());
|
|
}
|
|
const auto& closest_symbol = symbol_table_.symbols()[index];
|
|
// Adjust the addend by adding the offset from the found symbol.
|
|
addend += address - closest_symbol.value();
|
|
} else {
|
|
index = current_section_->index();
|
|
addend += address;
|
|
}
|
|
} else {
|
|
auto* const kv =
|
|
portion_and_section_by_label_.Lookup(reloc.target_label);
|
|
RELEASE_ASSERT(kv != nullptr);
|
|
const auto [section, portion] = kv->value;
|
|
const intptr_t target_address =
|
|
section->memory_address() + portion->offset;
|
|
if (emitted_subtrahend) {
|
|
intptr_t starting_index = symbol_table_.IndexForLabel(portion->label);
|
|
index = FindClosestSymbolIndexTo(0, section, portion, starting_index);
|
|
if (index < 0) {
|
|
FATAL("Cannot find any symbol in section %" Pd "",
|
|
section->index());
|
|
}
|
|
const auto& closest_symbol = symbol_table_.symbols()[index];
|
|
// Adjust the addend by adding the offset from the found symbol.
|
|
addend += target_address - closest_symbol.value();
|
|
} else {
|
|
index = section->index();
|
|
addend += target_address;
|
|
}
|
|
ASSERT(index != mach_o::NO_SECT);
|
|
}
|
|
if (!Utils::IsUint(mach_o::RELOC_METADATA_INDEX_BITS, index)) {
|
|
FATAL("Could not convert target label %" Pd
|
|
" of relocation, got %s index %" Pd " and addend %#" Px "",
|
|
reloc.target_label,
|
|
(target_metadata & mach_o::RELOC_EXTERN) != 0 ? "symbol"
|
|
: "section",
|
|
index, addend);
|
|
}
|
|
target_metadata |= index;
|
|
#if defined(TARGET_ARCH_X64)
|
|
target_metadata |= mach_o::RELOC_TYPE_X64_UNSIGNED;
|
|
#elif defined(TARGET_ARCH_ARM64)
|
|
target_metadata |= mach_o::RELOC_TYPE_ARM64_UNSIGNED;
|
|
#else
|
|
// Relocatable objects aren't handled for this architecture.
|
|
UNREACHABLE();
|
|
#endif
|
|
target_metadata |= RelocationSize(reloc.size_in_bytes);
|
|
EnsureCurrentRelocations()->Add({section_offset, target_metadata});
|
|
}
|
|
if (!Utils::IsInt(reloc.size_in_bytes * kBitsPerByte, addend)) {
|
|
FATAL("Calculated addend for relocation too large: %#" Px "", addend);
|
|
}
|
|
EnsureCurrentRelocationAddends()->Add(addend);
|
|
}
|
|
|
|
Zone* const zone_;
|
|
const bool output_is_relocatable_object_;
|
|
const MachOSymbolTable& symbol_table_;
|
|
intptr_t file_offset_;
|
|
// A mapping of portion labels to the associated section and portion.
|
|
DirectChainedHashMap<IntKeyRawPointerValueTrait<
|
|
std::pair<const MachOSection*, const MachOSection::Portion*>>>
|
|
portion_and_section_by_label_;
|
|
|
|
// Internal state fields used by ConvertRelocation/ConvertRelocationLabel.
|
|
const MachOSection* current_section_ = nullptr;
|
|
const MachOSection::Portion* current_portion_ = nullptr;
|
|
MachORelocationsArray* current_relocations_ = nullptr;
|
|
MachORelocationAddendsArray* current_relocation_addends_ = nullptr;
|
|
intptr_t starting_index_for_self_ = -1;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(RelocationsConverter);
|
|
};
|
|
|
|
void MachOHeader::ComputeOffsets() {
|
|
// First, set the offsets of the load commands in the header.
|
|
intptr_t header_offset = SizeWithoutLoadCommands();
|
|
for (auto* const c : commands_) {
|
|
ASSERT(
|
|
Utils::IsAligned(header_offset, MachOCommand::kLoadCommandAlignment));
|
|
c->set_header_offset(header_offset);
|
|
header_offset += c->cmdsize();
|
|
}
|
|
|
|
// Next, set the offsets of the contents of load commands with post-header
|
|
// content (segments, symbol tables, etc.).
|
|
ContentOffsetsVisitor visitor(zone());
|
|
VisitContents(&visitor);
|
|
|
|
// Finally, for relocatable objects, convert the relocations into appropriate
|
|
// relocation entries and addends and also set the offsets at which relocation
|
|
// information is stored for sections, as this information is written into
|
|
// the file after the other contents.
|
|
//
|
|
// For other types of output, there is no converted relocation information (as
|
|
// the relocatable values are fully computed), so this visitor sets the
|
|
// section fields for relocations to reflect this.
|
|
RelocationsConverter relocs_visitor(zone_, *this, visitor.file_offset);
|
|
VisitContents(&relocs_visitor);
|
|
}
|
|
|
|
void MachOSymbolTable::Initialize(SharedObjectWriter::Type type,
|
|
const char* path,
|
|
const GrowableArray<MachOSection*>& sections,
|
|
bool is_stripped) {
|
|
using SnapshotType = SharedObjectWriter::Type;
|
|
// Not idempotent.
|
|
ASSERT(!num_local_symbols_is_set());
|
|
|
|
// If symbolic debugging symbols are emitted, then any section
|
|
// symbols are marked as alternate entries in favor of the symbolic
|
|
// debugging symbols.
|
|
const intptr_t desc =
|
|
(type == SnapshotType::Object || is_stripped) ? 0 : mach_o::N_ALT_ENTRY;
|
|
|
|
// For unstripped symbol tables, we do two initial passes. In the first
|
|
// pass, we add section symbols for local static symbols.
|
|
if (!is_stripped) {
|
|
for (intptr_t i = 0, n = sections.length(); i < n; ++i) {
|
|
auto* const section = sections[i];
|
|
for (const auto& portion : section->portions()) {
|
|
if (portion.symbols != nullptr) {
|
|
for (const auto& symbol_data : *portion.symbols) {
|
|
AddSymbol(symbol_data.name, mach_o::N_SECT, section, desc,
|
|
portion.offset + symbol_data.offset, symbol_data.label);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// The second pass adds appropriate symbolic debugging symbols. This pass
|
|
// is skipped for relocatable objects.
|
|
if (type != SnapshotType::Object) {
|
|
using Type = SharedObjectWriter::SymbolData::Type;
|
|
if (path != nullptr) {
|
|
// The value of the OSO symbolic debugging symbol is the mtime of the
|
|
// object file. However, clang may warn about a mismatch if this is not
|
|
// 0 and differs from the actual mtime of the object file.
|
|
AddSymbol(path, mach_o::N_OSO, /*section=*/nullptr,
|
|
/*description=*/1, /*section_offset_or_value=*/0);
|
|
}
|
|
auto add_symbolic_debugging_symbols = [&](const char* name, Type type,
|
|
const MachOSection* section,
|
|
intptr_t offset, intptr_t size,
|
|
bool is_global) {
|
|
switch (type) {
|
|
case Type::Function: {
|
|
AddSymbol("", mach_o::N_BNSYM, section, /*description=*/0, offset);
|
|
AddSymbol(name, mach_o::N_FUN, section, /*description=*/0, offset);
|
|
// The size is output as an unnamed N_FUN symbol with no section
|
|
// following the actual N_FUN symbol.
|
|
AddSymbol("", mach_o::N_FUN, /*section=*/nullptr, /*description=*/0,
|
|
size);
|
|
AddSymbol("", mach_o::N_ENSYM, section, /*description=*/0,
|
|
offset + size);
|
|
|
|
break;
|
|
}
|
|
case Type::Section:
|
|
case Type::Object: {
|
|
if (is_global) {
|
|
AddSymbol(name, mach_o::N_GSYM, /*section=*/nullptr,
|
|
/*description=*/0,
|
|
/*section_offset_or_value=*/0);
|
|
} else {
|
|
AddSymbol(name, mach_o::N_STSYM, section,
|
|
/*description=*/0, offset);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
for (intptr_t i = 0, n = sections.length(); i < n; ++i) {
|
|
auto* const section = sections[i];
|
|
// We handle global symbols for text sections slightly differently than
|
|
// those for other sections.
|
|
const bool is_text_section = section->HasName(mach_o::SECT_TEXT);
|
|
for (const auto& portion : section->portions()) {
|
|
if (portion.symbol_name != nullptr) {
|
|
// Matching the symbolic debugging symbols created for assembled
|
|
// snapshots.
|
|
auto const type = is_text_section ? Type::Function : Type::Section;
|
|
// The "size" of a function symbol created for start of a text
|
|
// portion is up to the first function symbol.
|
|
auto const size = is_text_section && portion.symbols != nullptr
|
|
? portion.symbols->At(0).offset
|
|
: portion.size;
|
|
add_symbolic_debugging_symbols(portion.symbol_name, type, section,
|
|
portion.offset, size,
|
|
/*is_global=*/true);
|
|
}
|
|
if (portion.symbols != nullptr) {
|
|
for (const auto& symbol_data : *portion.symbols) {
|
|
add_symbolic_debugging_symbols(
|
|
symbol_data.name, symbol_data.type, section,
|
|
portion.offset + symbol_data.offset, symbol_data.size,
|
|
/*is_global=*/false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
set_num_local_symbols(num_symbols());
|
|
|
|
// In the final pass, we add external symbols for section global symbols
|
|
// (so added to both stripped and unstripped symbol tables).
|
|
for (intptr_t i = 0, n = sections.length(); i < n; ++i) {
|
|
auto* const section = sections[i];
|
|
for (const auto& portion : section->portions()) {
|
|
if (portion.symbol_name != nullptr) {
|
|
AddSymbol(portion.symbol_name, mach_o::N_SECT | mach_o::N_EXT, section,
|
|
desc, portion.offset, portion.label);
|
|
}
|
|
}
|
|
}
|
|
set_num_external_symbols(num_symbols() - num_local_symbols());
|
|
}
|
|
|
|
} // namespace dart
|
|
|
|
#endif // defined(DART_PRECOMPILER)
|