blob: 9bc67655b64fc380401e72866f13f10edcd945b2 [file] [log] [blame]
// 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.");
#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)
#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
// 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>;
public:
explicit MachOWriteStream(const MachOWriter& macho)
: SharedObjectWriter::WriteStream(), macho_(macho) {}
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;
protected:
const MachOWriter& macho_;
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 ZoneAllocated {
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);
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,
intptr_t type = mach_o::S_REGULAR,
intptr_t attributes = mach_o::S_NO_ATTRIBUTES,
bool has_contents = true,
intptr_t alignment = MachOWriter::kPageSize)
: MachOContents(/*needs_offset=*/has_contents,
/*in_segment=*/true),
name_(name),
flags_(mach_o::SectionFlags(type, attributes)),
alignment_(alignment),
portions_(zone, 0) {
ASSERT(strlen(name) <= sizeof(SectionType::sectname));
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_; }
bool HasName(const char* name) const { return strcmp(name_, name) == 0; }
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;
}
void WriteSelf(MachOWriteStream* stream) const override {
if (!HasContents()) return;
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());
}
}
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); }
private:
uint32_t HeaderInfoSize() const { return sizeof(SectionType); }
// Called during MachOSegment::WriteLoadCommand.
void WriteHeaderInfo(MachOWriteStream* stream, const char* segname) 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(0, 0); // No relocation entries.
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 decltype(SectionType::flags) flags_ = 0;
const intptr_t alignment_;
GrowableArray<Portion> portions_;
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
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,
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),
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 { return 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 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 {
return Utils::RoundUp(UnpaddedMemorySize(), Alignment());
}
// 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, name_);
}
}
MachOSection* FindSection(const char* name) const {
for (auto* const c : contents_) {
if (auto* const s = c->AsMachOSection()) {
if (s->HasName(name)) 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:
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, &current)) {
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, &current)) {
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, &current)) {
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;
explicit MachOSymbolTable(Zone* zone)
: MachOCommand(kCommandCode),
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,
intptr_t n_sect,
intptr_t n_desc,
uword n_value)
: name_index(n_idx),
type(n_type),
section_index(n_sect),
description(n_desc),
value(n_value) {
ASSERT(Utils::IsUint(32, n_idx));
ASSERT(Utils::IsUint(8, n_type));
ASSERT(Utils::IsUint(8, n_sect));
ASSERT(Utils::IsUint(16, n_desc));
ASSERT(Utils::IsUint(sizeof(compiler::target::uword) * kBitsPerByte,
n_value));
}
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));
}
// 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 not equal to mach_o::NO_SECT.
// The sections are indexed by their appearance in the load commands
// (e.g., the first section of the first segment command that contains
// sections has index 1, and the first section of the second segment command
// that contains sections has index [k + 1] if the first segment contains
// [k] sections).
uint8_t section_index;
// See the mach_o::N_* constants for the encoding of this field.
uint16_t description;
// For symbols where section_index != macho_o::NO_SECT, this is the section
// offset until finalization, when it is converted to the offset into the
// snapshot.
compiler::target::uword 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,
intptr_t section_index,
intptr_t description,
uword 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_index, description, 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 = by_label_index_.Lookup(label) - 1;
if (symbols_index < 0) return nullptr; // Not found.
return &symbols_[symbols_index];
}
void Initialize(const char* path,
const GrowableArray<MachOSection*>& sections,
bool is_stripped);
void UpdateSectionIndices(const GrowableArray<intptr_t>& index_map) {
const intptr_t map_size = index_map.length();
#if defined(DEBUG)
for (intptr_t i = 0; i < map_size; i++) {
const intptr_t new_index = index_map[i];
ASSERT(Utils::IsUint(8, new_index));
ASSERT(new_index < map_size);
if (i == mach_o::NO_SECT) {
ASSERT_EQUAL(new_index, mach_o::NO_SECT);
} else {
ASSERT(new_index != mach_o::NO_SECT);
}
}
#endif
for (auto& symbol : symbols_) {
const uint8_t old_index = symbol.section_index;
ASSERT(old_index < map_size);
symbol.section_index = index_map[old_index];
}
}
void Finalize(const GrowableArray<uword>& address_map) {
const intptr_t map_size = address_map.length();
#if defined(DEBUG)
for (intptr_t i = 0; i < map_size; i++) {
if (i == mach_o::NO_SECT) {
// The entry for NO_SECT must be 0 so that symbols with that index,
// like global symbols, are unchanged.
ASSERT_EQUAL(address_map[mach_o::NO_SECT], 0);
} else {
// No valid section begins at the start of the snapshot.
ASSERT(address_map[i] > 0);
}
}
#endif
for (auto& symbol : symbols_) {
ASSERT(symbol.section_index < map_size);
symbol.value += address_map[symbol.section_index];
}
}
uint32_t cmdsize() const override { return sizeof(mach_o::symtab_command); }
intptr_t SelfMemorySize() const override {
return SymbolsSize() + strings_.FileSize();
}
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;
explicit MachODynamicSymbolTable(const MachOSymbolTable& table)
: MachOCommand(kCommandCode), 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; }
void Accept(Visitor* visitor) override {
visitor->VisitMachODynamicSymbolTable(this);
}
private:
static constexpr intptr_t kUnusedOffsetCountPairs = 6;
const MachOSymbolTable& table_;
DISALLOW_COPY_AND_ASSIGN(MachODynamicSymbolTable);
};
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,
const char* identifier,
const char* path,
Dwarf* dwarf)
: MachOContents(),
zone_(zone),
type_(type),
is_stripped_(is_stripped),
identifier_(identifier != nullptr ? identifier : ""),
path_(path),
dwarf_(dwarf),
commands_(zone, 0),
full_symtab_(zone) {
#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_;
}
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 true; }
intptr_t Alignment() const override { return compiler::target::kWordSize; }
// The header uses the default MemorySize() implementation, because
// VisitChildren() doesn't visit the load commands and so the header is
// not considered to contain nested content.
//
// 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 {
intptr_t size = SizeWithoutLoadCommands();
for (auto* const command : commands_) {
size += command->cmdsize();
}
return size;
}
uint32_t filetype() const {
if (type_ == SnapshotType::Snapshot) {
return mach_o::MH_DYLIB;
}
ASSERT(type_ == SnapshotType::DebugInfo);
return mach_o::MH_DSYM;
}
uint32_t flags() const {
if (type_ == SnapshotType::Snapshot) {
return mach_o::MH_NOUNDEFS | mach_o::MH_DYLDLINK |
mach_o::MH_NO_REEXPORTED_DYLIBS;
}
ASSERT(type_ == SnapshotType::DebugInfo);
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 {
auto* const s = FindSegment(segname);
if (s == nullptr) return nullptr;
return s->FindSection(sectname);
}
MachOSegment* EnsureTextSegment() {
if (text_segment_ == nullptr) {
// Make sure it didn't get added outside this method.
ASSERT(FindSegment(mach_o::SEG_TEXT) == nullptr);
auto const vm_protection = mach_o::VM_PROT_READ | mach_o::VM_PROT_EXECUTE;
text_segment_ = new (zone())
MachOSegment(zone(), mach_o::SEG_TEXT, 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, 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 VisitSegments(Visitor* visitor) {
for (auto* const c : commands_) {
if (!c->IsMachOSegment()) continue;
c->Accept(visitor);
}
}
private:
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_;
// 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_;
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());
// 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;
}
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)
: SharedObjectWriter(zone, stream, type, dwarf),
header_(*new (zone) MachOHeader(
zone,
type,
IsStripped(dwarf),
FLAG_macho_install_name != nullptr ? FLAG_macho_install_name : id,
path,
dwarf)) {}
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);
if (text_section == nullptr) {
const bool has_contents = type_ == Type::Snapshot;
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::S_REGULAR, attributes, has_contents);
text_segment->AddContents(text_section);
}
text_section->AddPortion(bytes, size, relocations, symbols, name, label);
}
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);
if (const_section == nullptr) {
const bool has_contents = type_ == Type::Snapshot;
const_section =
new (zone()) MachOSection(zone(), mach_o::SECT_CONST, 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);
}
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);
};
void MachOWriter::Finalize() {
header_.Finalize();
if (header_.HasCommand(MachOCodeSignature::kCommandCode)) {
HashingMachOWriteStream wrapped(zone_, unwrapped_stream_, *this);
WriteVisitor visitor(&wrapped);
header_.VisitSegments(&visitor);
} else {
NonHashingMachOWriteStream wrapped(unwrapped_stream_, *this);
WriteVisitor visitor(&wrapped);
header_.VisitSegments(&visitor);
}
}
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 uint32_t HashPortion(const MachOSection::Portion& portion) {
if (portion.bytes == nullptr) return 0;
const uint32_t hash = Utils::StringHash(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 32 bits is a
// hash of the contents of the following segments in order:
//
// .text(VM) | .text(Isolate) | .rodata(VM) | .rodata(Isolate)
//
// Any component of the build ID which does not have an associated section
// in the output is kept as 0.
void MachOHeader::GenerateUuid() {
// 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.
auto* const text_segment = FindSegment(mach_o::SEG_TEXT);
if (text_segment == nullptr) return;
auto* const text_section = text_segment->FindSection(mach_o::SECT_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 vm_instructions =
text_section->FindPortion(kVmSnapshotInstructionsAsmSymbol);
auto* const isolate_instructions =
text_section->FindPortion(kIsolateSnapshotInstructionsAsmSymbol);
// All MachO snapshots have at least one of the two instruction sections.
ASSERT(vm_instructions != nullptr || isolate_instructions != nullptr);
auto* const data_section = text_segment->FindSection(mach_o::SECT_CONST);
auto* const vm_data =
data_section == nullptr
? nullptr
: data_section->FindPortion(kVmSnapshotDataAsmSymbol);
auto* const isolate_data =
data_section == nullptr
? nullptr
: data_section->FindPortion(kIsolateSnapshotDataAsmSymbol);
uint32_t hashes[4];
hashes[0] = vm_instructions == nullptr ? 0 : HashPortion(*vm_instructions);
hashes[1] =
isolate_instructions == nullptr ? 0 : HashPortion(*isolate_instructions);
hashes[2] = vm_data == nullptr ? 0 : HashPortion(*vm_data);
hashes[3] = isolate_data == nullptr ? 0 : 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. 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;
auto* const data_segment = new (zone())
MachOSegment(zone(), 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::S_ZEROFILL,
mach_o::S_NO_ATTRIBUTES, /*has_contents=*/false,
/*alignment=*/compiler::target::kWordSize);
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, kVmSnapshotInstructionsAsmSymbol) == 0) {
size = BSS::kVmEntryCount * compiler::target::kWordSize;
symbol_name = kVmSnapshotBssAsmSymbol;
label = SharedObjectWriter::kVmBssLabel;
} else if (strcmp(portion.symbol_name,
kIsolateSnapshotInstructionsAsmSymbol) == 0) {
size = BSS::kIsolateGroupEntryCount * compiler::target::kWordSize;
symbol_name = kIsolateSnapshotBssAsmSymbol;
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) {
// 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);
// Each instructions image starts with the Image header and the
// InstructionsSection header.
const intptr_t header_size =
Image::kHeaderSize + compiler::target::InstructionsSection::HeaderSize();
// 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.
// 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 add_unwind_section =
[&](MachOSegment* segment, const char* sectname,
const ZoneWriteStream& stream,
const SharedObjectWriter::RelocationArray* relocations = nullptr) {
// Not idempotent.
ASSERT(segment->FindSection(sectname) == nullptr);
auto* const section = new (zone())
MachOSection(zone(), sectname,
use_zerofill ? mach_o::S_ZEROFILL : mach_o::S_REGULAR,
mach_o::S_NO_ATTRIBUTES, !use_zerofill, alignment);
section->AddPortion(use_zerofill ? nullptr : stream.buffer(),
stream.bytes_written(),
use_zerofill ? nullptr : relocations);
segment->AddContents(section);
};
ASSERT(text_segment_ != nullptr);
if (auto* const text_section =
text_segment_->FindSection(mach_o::SECT_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);
#if defined(DART_TARGET_OS_MACOS) && defined(TARGET_ARCH_ARM64)
GenerateCompactUnwindingInformation(dwarf_stream, fdes);
auto* const sectname = mach_o::SECT_UNWIND_INFO;
#else
Dwarf::WriteCallFrameInformationRecords(&dwarf_stream, fdes);
auto* const sectname = mach_o::SECT_EH_FRAME;
#endif
add_unwind_section(text_segment_, sectname, stream,
dwarf_stream.relocations());
}
#if defined(UNWINDING_RECORDS_WINDOWS_PRECOMPILER)
// Append Windows unwinding instructions as a __unwind_info section at
// the end of any executable segments.
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());
add_unwind_section(segment, mach_o::SECT_UNWIND_INFO, stream);
}
}
}
#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 defined(DART_TARGET_OS_MACOS) || defined(DART_TARGET_OS_MACOS_IOS)
ASSERT(!HasCommand(MachOBuildVersion::kCommandCode));
ASSERT(!HasCommand(MachOLoadDylib::kCommandCode));
ASSERT(!HasCommand(MachORunPath::kCommandCode));
commands_.Add(new (zone_) MachOBuildVersion());
commands_.Add(MachOLoadDylib::CreateLoadSystemDylib(zone_));
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
}
}
void MachOHeader::InitializeSymbolTables() {
// Not idempotent.
ASSERT_EQUAL(full_symtab_.num_symbols(), 0);
ASSERT(!HasCommand(MachOSymbolTable::kCommandCode));
// Grab all the sections in order.
GrowableArray<MachOSection*> sections(zone_, 0);
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()) {
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(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());
table->Initialize(path_, sections, is_stripped_);
}
commands_.Add(table);
// For snapshots, include a dynamic symbol table as well.
if (type_ == SnapshotType::Snapshot) {
auto* const dynamic_symtab = new (zone()) MachODynamicSymbolTable(*table);
commands_.Add(dynamic_symtab);
}
}
void MachOHeader::FinalizeDwarfSections() {
if (dwarf_ == nullptr) return;
// Currently we only output DWARF information involving code.
#if defined(DEBUG)
auto* const text_segment = FindSegment(mach_o::SEG_TEXT);
ASSERT(text_segment != nullptr);
ASSERT(text_segment->FindSection(mach_o::SECT_TEXT) != nullptr);
#endif
// 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;
auto* const dwarf_segment = new (zone()) MachOSegment(
zone(), 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));
auto* const section = new (zone())
MachOSection(zone(), name, mach_o::S_REGULAR, mach_o::S_ATTR_DEBUG,
/*has_contents=*/true, alignment);
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. A linkedit segment
// is created later to contain the non-header contents of these commands.
GrowableArray<MachOCommand*> linkedit_commands(zone_, 0);
// Maps segments to the section count and old initial section index for
// that segment. (Sections are not reordered during this, so this is
// all that's needed to calculate new section indices.)
using SegmentMapTrait =
RawPointerKeyValueTrait<const MachOSegment,
std::pair<intptr_t, intptr_t>>;
DirectChainedHashMap<SegmentMapTrait> section_info(zone_, num_commands);
intptr_t num_sections = 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()) {
const intptr_t count = s->NumSections();
if (count != 0) {
// Section indices start from 1.
section_info.Insert({s, {count, num_sections + 1}});
num_sections += count;
}
if (s->HasName(mach_o::SEG_TEXT)) {
ASSERT(text_segment == s);
} else if (s->ContainsSymbols()) {
symbol_segments.Add(s);
} else {
other_segments.Add(s);
}
} else if (!command->HasContents()) {
header_only_commands.Add(command);
} else {
linkedit_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());
auto* const linkedit_segment =
new (zone_) MachOSegment(zone_, 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);
segments.Add(linkedit_segment);
// 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 map old section indices to new ones. Include
// a map of mach_o::NO_SECT to mach_o::NO_SECT so that changing the section
// index on a non-section symbol is a no-op.
GrowableArray<intptr_t> index_map(zone_, num_sections + 1);
index_map.FillWith(mach_o::NO_SECT, 0, num_sections + 1);
// Section indices start from 1.
intptr_t current_section_index = 1;
for (auto* const s : segments) {
reordered_commands.Add(s);
auto* const kv = section_info.Lookup(s);
if (kv != nullptr) {
const auto& [num_sections, old_start] = SegmentMapTrait::ValueOf(*kv);
ASSERT(num_sections > 0); // Otherwise it's not in the map.
ASSERT(old_start != mach_o::NO_SECT);
for (intptr_t i = 0; i < num_sections; ++i) {
ASSERT(current_section_index != mach_o::NO_SECT);
index_map[old_start + i] = 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);
// This must be true for uses of the map to be correct.
ASSERT_EQUAL(index_map[mach_o::NO_SECT], mach_o::NO_SECT);
#if defined(DEBUG)
for (intptr_t i = 1; i < num_sections; ++i) {
ASSERT(index_map[i] != mach_o::NO_SECT);
}
#endif
// Update the section indices of any section-owned symbols.
full_symtab_.UpdateSectionIndices(index_map);
auto* const table = IncludedSymbolTable();
if (table != &full_symtab_) {
ASSERT(is_stripped_);
table->UpdateSectionIndices(index_map);
}
}
struct ContentOffsetsVisitor : public MachOContents::Visitor {
explicit ContentOffsetsVisitor(Zone* zone) : address_map(zone, 1) {
// Add NO_SECT -> 0 mapping.
address_map.Add(0);
}
void Default(MachOContents* contents) {
ASSERT_EQUAL(contents->IsMachOHeader(), file_offset == 0);
ASSERT_EQUAL(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(), 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());
}
void VisitMachOSection(MachOSection* section) {
// Sections do not contain other sections, so the visitor can use the
// default behavior without worrying about adding to the address map in
// the wrong order.
Visitor::VisitMachOSection(section);
address_map.Add(section->memory_address());
}
// Maps indices of allocated sections in the section table to memory offsets.
// Note that sections are 1-indexed, with 0 (NO_SECT) mapping to 0.
GrowableArray<uword> address_map;
intptr_t file_offset = 0;
intptr_t memory_address = 0;
private:
DISALLOW_COPY_AND_ASSIGN(ContentOffsetsVisitor);
};
void MachOHeader::ComputeOffsets() {
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();
}
ContentOffsetsVisitor visitor(zone());
// All commands with non-header content should be part of a segment.
// In addition, the header is visited during the initial segment.
VisitSegments(&visitor);
// Finalize the dynamic symbol table, now that the file offset for the
// symbol table has been calculated.
// Entry for NO_SECT + 1-indexed entries for sections.
ASSERT_EQUAL(visitor.address_map.length(), NumSections() + 1);
// Adjust addresses in symbol tables as we now have section memory offsets.
full_symtab_.Finalize(visitor.address_map);
auto* const table = IncludedSymbolTable();
if (table != &full_symtab_) {
ASSERT(is_stripped_);
table->Finalize(visitor.address_map);
}
}
void MachOSymbolTable::Initialize(const char* path,
const GrowableArray<MachOSection*>& sections,
bool is_stripped) {
// 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 = 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];
const intptr_t section_index = i + 1; // 1-indexed, as 0 is NO_SECT.
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_index, desc,
portion.offset + symbol_data.offset, symbol_data.label);
}
}
}
}
// In the second pass, we add appropriate symbolic debugging symbols.
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, so just use 0.
AddSymbol(path, mach_o::N_OSO, /*section_index=*/0,
/*description=*/1, /*value=*/0);
}
auto add_symbolic_debugging_symbols =
[&](const char* name, Type type, intptr_t section_index,
intptr_t offset, intptr_t size, bool is_global) {
switch (type) {
case Type::Function: {
AddSymbol("", mach_o::N_BNSYM, section_index, /*description=*/0,
offset);
AddSymbol(name, mach_o::N_FUN, section_index, /*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, mach_o::NO_SECT, /*description=*/0,
size);
AddSymbol("", mach_o::N_ENSYM, section_index, /*description=*/0,
offset + size);
break;
}
case Type::Section:
case Type::Object: {
if (is_global) {
AddSymbol(name, mach_o::N_GSYM, mach_o::NO_SECT,
/*description=*/0,
/*value=*/0);
} else {
AddSymbol(name, mach_o::N_STSYM, section_index,
/*description=*/0, offset);
}
break;
}
}
};
for (intptr_t i = 0, n = sections.length(); i < n; ++i) {
auto* const section = sections[i];
const intptr_t section_index = i + 1; // 1-indexed, as 0 is NO_SECT.
// 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_index, 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_index,
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];
const intptr_t section_index = i + 1; // 1-indexed, as 0 is NO_SECT.
for (const auto& portion : section->portions()) {
if (portion.symbol_name != nullptr) {
AddSymbol(portion.symbol_name, mach_o::N_SECT | mach_o::N_EXT,
section_index, desc, portion.offset, portion.label);
}
}
}
set_num_external_symbols(num_symbols() - num_local_symbols());
}
} // namespace dart
#endif // defined(DART_PRECOMPILER)