aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJakub Konka <kubkon@jakubkonka.com>2020-11-27 21:49:06 +0100
committerGitHub <noreply@github.com>2020-11-27 21:49:06 +0100
commit3bc1c719bd632e6baccb574bf75cb93f0bdef08f (patch)
tree288fea5f073ec13c4b435a853e40f7c1d1e90ab4 /src
parentdaf6c0da5b2d96530dea2d2a907f2b2ba55c8ba6 (diff)
parent5ed76268c9f0ecca8c5d62cb7e56da05aa8f1f7c (diff)
downloadzig-3bc1c719bd632e6baccb574bf75cb93f0bdef08f.tar.gz
zig-3bc1c719bd632e6baccb574bf75cb93f0bdef08f.zip
Merge pull request #7231 from kubkon/stage2-arm-macos
stage2: Hello, Silicon!\n
Diffstat (limited to 'src')
-rw-r--r--src/codegen.zig143
-rw-r--r--src/codegen/aarch64.zig89
-rw-r--r--src/link.zig24
-rw-r--r--src/link/MachO.zig481
-rw-r--r--src/link/MachO/CodeSignature.zig182
-rw-r--r--src/main.zig3
6 files changed, 727 insertions, 195 deletions
diff --git a/src/codegen.zig b/src/codegen.zig
index 48fda6f0d5..74800aa22e 100644
--- a/src/codegen.zig
+++ b/src/codegen.zig
@@ -1683,15 +1683,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
const got_addr = got.addr + func.owner_decl.link.macho.offset_table_index * @sizeOf(u64);
switch (arch) {
.x86_64 => {
- // Here, we store the got address in %rax, and then call %rax
- // movabsq [addr], %rax
try self.genSetReg(inst.base.src, .rax, .{ .memory = got_addr });
// callq *%rax
- try self.code.ensureCapacity(self.code.items.len + 2);
self.code.appendSliceAssumeCapacity(&[2]u8{ 0xff, 0xd0 });
},
.aarch64 => {
try self.genSetReg(inst.base.src, .x30, .{ .memory = got_addr });
+ // blr x30
writeInt(u32, try self.code.addManyAsArray(4), Instruction.blr(.x30).toU32());
},
else => unreachable, // unsupported architecture on MachO
@@ -2586,10 +2584,82 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
},
.register => return self.fail(src, "TODO implement genSetReg for aarch64 {}", .{mcv}),
.memory => |addr| {
- // The value is in memory at a hard-coded address.
- // If the type is a pointer, it means the pointer address is at this memory location.
- try self.genSetReg(src, reg, .{ .immediate = addr });
- mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ldr(reg, .{ .rn = reg }).toU32());
+ if (self.bin_file.options.pie) {
+ // For MachO, the binary, with the exception of object files, has to be a PIE.
+ // Therefore we cannot load an absolute address.
+ // Instead, we need to make use of PC-relative addressing.
+ // TODO This needs to be optimised in the stack usage (perhaps use a shadow stack
+ // like described here:
+ // https://community.arm.com/developer/ip-products/processors/b/processors-ip-blog/posts/using-the-stack-in-aarch64-implementing-push-and-pop)
+ // TODO As far as branching is concerned, instead of saving the return address
+ // in a register, I'm thinking here of immitating x86_64, and having the address
+ // passed on the stack.
+ if (reg.id() == 0) { // x0 is special-cased
+ // str x28, [sp, #-16]
+ mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.str(.x28, Register.sp, .{
+ .offset = Instruction.Offset.imm_pre_index(-16),
+ }).toU32());
+ // adr x28, #8
+ mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.adr(.x28, 8).toU32());
+ if (self.bin_file.cast(link.File.MachO)) |macho_file| {
+ try macho_file.pie_fixups.append(self.bin_file.allocator, .{
+ .address = addr,
+ .start = self.code.items.len,
+ .len = 4,
+ });
+ } else {
+ return self.fail(src, "TODO implement genSetReg for PIE on this platform", .{});
+ }
+ // b [label]
+ mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.b(0).toU32());
+ // mov r, x0
+ mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.orr(reg, .x0, Instruction.RegisterShift.none()).toU32());
+ // ldr x28, [sp], #16
+ mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ldr(.x28, .{
+ .rn = Register.sp,
+ .offset = Instruction.Offset.imm_post_index(16),
+ }).toU32());
+ } else {
+ // str x28, [sp, #-16]
+ mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.str(.x28, Register.sp, .{
+ .offset = Instruction.Offset.imm_pre_index(-16),
+ }).toU32());
+ // str x0, [sp, #-16]
+ mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.str(.x0, Register.sp, .{
+ .offset = Instruction.Offset.imm_pre_index(-16),
+ }).toU32());
+ // adr x28, #8
+ mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.adr(.x28, 8).toU32());
+ if (self.bin_file.cast(link.File.MachO)) |macho_file| {
+ try macho_file.pie_fixups.append(self.bin_file.allocator, .{
+ .address = addr,
+ .start = self.code.items.len,
+ .len = 4,
+ });
+ } else {
+ return self.fail(src, "TODO implement genSetReg for PIE on this platform", .{});
+ }
+ // b [label]
+ mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.b(0).toU32());
+ // mov r, x0
+ mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.orr(reg, .x0, Instruction.RegisterShift.none()).toU32());
+ // ldr x0, [sp], #16
+ mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ldr(.x0, .{
+ .rn = Register.sp,
+ .offset = Instruction.Offset.imm_post_index(16),
+ }).toU32());
+ // ldr x28, [sp], #16
+ mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ldr(.x28, .{
+ .rn = Register.sp,
+ .offset = Instruction.Offset.imm_post_index(16),
+ }).toU32());
+ }
+ } else {
+ // The value is in memory at a hard-coded address.
+ // If the type is a pointer, it means the pointer address is at this memory location.
+ try self.genSetReg(src, reg, .{ .immediate = addr });
+ mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ldr(reg, .{ .rn = reg }).toU32());
+ }
},
else => return self.fail(src, "TODO implement genSetReg for aarch64 {}", .{mcv}),
},
@@ -2766,7 +2836,64 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
self.code.appendSliceAssumeCapacity(&[_]u8{ 0x8B, R });
},
.memory => |x| {
- if (x <= math.maxInt(u32)) {
+ if (self.bin_file.options.pie) {
+ // For MachO, the binary, with the exception of object files, has to be a PIE.
+ // Therefore, we cannot load an absolute address.
+ assert(x > math.maxInt(u32)); // 32bit direct addressing is not supported by MachO.
+ // The plan here is to use unconditional relative jump to GOT entry, where we store
+ // pre-calculated and stored effective address to load into the target register.
+ // We leave the actual displacement information empty (0-padded) and fixing it up
+ // later in the linker.
+ if (reg.id() == 0) { // %rax is special-cased
+ try self.code.ensureCapacity(self.code.items.len + 5);
+ if (self.bin_file.cast(link.File.MachO)) |macho_file| {
+ try macho_file.pie_fixups.append(self.bin_file.allocator, .{
+ .address = x,
+ .start = self.code.items.len,
+ .len = 5,
+ });
+ } else {
+ return self.fail(src, "TODO implement genSetReg for PIE on this platform", .{});
+ }
+ // call [label]
+ self.code.appendSliceAssumeCapacity(&[_]u8{
+ 0xE8,
+ 0x0,
+ 0x0,
+ 0x0,
+ 0x0,
+ });
+ } else {
+ try self.code.ensureCapacity(self.code.items.len + 10);
+ // push %rax
+ self.code.appendSliceAssumeCapacity(&[_]u8{0x50});
+ if (self.bin_file.cast(link.File.MachO)) |macho_file| {
+ try macho_file.pie_fixups.append(self.bin_file.allocator, .{
+ .address = x,
+ .start = self.code.items.len,
+ .len = 5,
+ });
+ } else {
+ return self.fail(src, "TODO implement genSetReg for PIE on this platform", .{});
+ }
+ // call [label]
+ self.code.appendSliceAssumeCapacity(&[_]u8{
+ 0xE8,
+ 0x0,
+ 0x0,
+ 0x0,
+ 0x0,
+ });
+ // mov %r, %rax
+ self.code.appendSliceAssumeCapacity(&[_]u8{
+ 0x48,
+ 0x89,
+ 0xC0 | @as(u8, reg.id()),
+ });
+ // pop %rax
+ self.code.appendSliceAssumeCapacity(&[_]u8{0x58});
+ }
+ } else if (x <= math.maxInt(u32)) {
// Moving from memory to a register is a variant of `8B /r`.
// Since we're using 64-bit moves, we require a REX.
// This variant also requires a SIB, as it would otherwise be RIP-relative.
diff --git a/src/codegen/aarch64.zig b/src/codegen/aarch64.zig
index 18fccb823e..33b4a14eda 100644
--- a/src/codegen/aarch64.zig
+++ b/src/codegen/aarch64.zig
@@ -19,6 +19,8 @@ pub const Register = enum(u6) {
w16, w17, w18, w19, w20, w21, w22, w23,
w24, w25, w26, w27, w28, w29, w30, wzr,
+ pub const sp = .xzr;
+
pub fn id(self: Register) u5 {
return @truncate(u5, @enumToInt(self));
}
@@ -195,6 +197,17 @@ test "FloatingPointRegister.toX" {
/// Represents an instruction in the AArch64 instruction set
pub const Instruction = union(enum) {
+ OrShiftedRegister: packed struct {
+ rd: u5,
+ rn: u5,
+ imm6: u6,
+ rm: u5,
+ n: u1,
+ shift: u2,
+ fixed: u5 = 0b01010,
+ opc: u2 = 0b01,
+ sf: u1,
+ },
MoveWideImmediate: packed struct {
rd: u5,
imm16: u16,
@@ -251,6 +264,7 @@ pub const Instruction = union(enum) {
pub fn toU32(self: Instruction) u32 {
return switch (self) {
+ .OrShiftedRegister => |v| @bitCast(u32, v),
.MoveWideImmediate => |v| @bitCast(u32, v),
.PCRelativeAddress => |v| @bitCast(u32, v),
.LoadStoreRegister => |v| @bitCast(u32, v),
@@ -379,8 +393,65 @@ pub const Instruction = union(enum) {
}
};
+ pub const RegisterShift = struct {
+ rn: u5,
+ imm6: u6,
+ shift: enum(u2) {
+ Lsl = 0,
+ Lsr = 1,
+ Asr = 2,
+ Ror = 3,
+ },
+
+ pub fn none() RegisterShift {
+ return .{
+ .rn = 0b11111,
+ .imm6 = 0,
+ .shift = .Lsl,
+ };
+ }
+ };
+
// Helper functions for assembly syntax functions
+ fn orShiftedRegister(
+ rd: Register,
+ rm: Register,
+ shift: RegisterShift,
+ invert: bool,
+ ) Instruction {
+ const n: u1 = if (invert) 1 else 0;
+ switch (rd.size()) {
+ 32 => {
+ return Instruction{
+ .OrShiftedRegister = .{
+ .rd = rd.id(),
+ .rn = shift.rn,
+ .imm6 = shift.imm6,
+ .rm = rm.id(),
+ .n = n,
+ .shift = @enumToInt(shift.shift),
+ .sf = 0,
+ },
+ };
+ },
+ 64 => {
+ return Instruction{
+ .OrShiftedRegister = .{
+ .rd = rd.id(),
+ .rn = shift.rn,
+ .imm6 = shift.imm6,
+ .rm = rm.id(),
+ .n = n,
+ .shift = @enumToInt(shift.shift),
+ .sf = 1,
+ },
+ };
+ },
+ else => unreachable, // unexpected register size
+ }
+ }
+
fn moveWideImmediate(
opc: u2,
rd: Register,
@@ -543,6 +614,16 @@ pub const Instruction = union(enum) {
};
}
+ // Bitwise (inclusive) OR of a register value
+
+ pub fn orr(rd: Register, rm: Register, shift: RegisterShift) Instruction {
+ return orShiftedRegister(rd, rm, shift, false);
+ }
+
+ pub fn orn(rd: Register, rm: Register, shift: RegisterShift) Instruction {
+ return orShiftedRegister(rd, rm, shift, true);
+ }
+
// Move wide (immediate)
pub fn movn(rd: Register, imm16: u16, shift: u6) Instruction {
@@ -653,6 +734,14 @@ test "serialize instructions" {
};
const testcases = [_]Testcase{
+ .{ // orr x0 x1
+ .inst = Instruction.orr(.x0, .x1, Instruction.RegisterShift.none()),
+ .expected = 0b1_01_01010_00_0_00001_000000_11111_00000,
+ },
+ .{ // orn x0 x1
+ .inst = Instruction.orn(.x0, .x1, Instruction.RegisterShift.none()),
+ .expected = 0b1_01_01010_00_1_00001_000000_11111_00000,
+ },
.{ // movz x1 #4
.inst = Instruction.movz(.x1, 4, 0),
.expected = 0b1_10_100101_00_0000000000000100_00001,
diff --git a/src/link.zig b/src/link.zig
index 064c178d66..e8a9f3d730 100644
--- a/src/link.zig
+++ b/src/link.zig
@@ -238,7 +238,29 @@ pub const File = struct {
pub fn makeExecutable(base: *File) !void {
switch (base.tag) {
- .coff, .elf, .macho => if (base.file) |f| {
+ .macho => if (base.file) |f| {
+ if (base.intermediary_basename != null) {
+ // The file we have open is not the final file that we want to
+ // make executable, so we don't have to close it.
+ return;
+ }
+ if (comptime std.Target.current.isDarwin() and std.Target.current.cpu.arch == .aarch64) {
+ if (base.options.target.cpu.arch != .aarch64) return; // If we're not targeting aarch64, nothing to do.
+ // XNU starting with Big Sur running on arm64 is caching inodes of running binaries.
+ // Any change to the binary will effectively invalidate the kernel's cache
+ // resulting in a SIGKILL on each subsequent run. Since when doing incremental
+ // linking we're modifying a binary in-place, this will end up with the kernel
+ // killing it on every subsequent run. To circumvent it, we will copy the file
+ // into a new inode, remove the original file, and rename the copy to match
+ // the original file. This is super messy, but there doesn't seem any other
+ // way to please the XNU.
+ const emit = base.options.emit orelse return;
+ try emit.directory.handle.copyFile(emit.sub_path, emit.directory.handle, emit.sub_path, .{});
+ }
+ f.close();
+ base.file = null;
+ },
+ .coff, .elf => if (base.file) |f| {
if (base.intermediary_basename != null) {
// The file we have open is not the final file that we want to
// make executable, so we don't have to close it.
diff --git a/src/link/MachO.zig b/src/link/MachO.zig
index 799a94bb20..d2a03cae18 100644
--- a/src/link/MachO.zig
+++ b/src/link/MachO.zig
@@ -7,6 +7,7 @@ const fs = std.fs;
const log = std.log.scoped(.link);
const macho = std.macho;
const codegen = @import("../codegen.zig");
+const aarch64 = @import("../codegen/aarch64.zig");
const math = std.math;
const mem = std.mem;
@@ -21,6 +22,7 @@ const Cache = @import("../Cache.zig");
const target_util = @import("../target.zig");
const Trie = @import("MachO/Trie.zig");
+const CodeSignature = @import("MachO/CodeSignature.zig");
pub const base_tag: File.Tag = File.Tag.macho;
@@ -33,6 +35,8 @@ const LoadCommand = union(enum) {
Dylinker: macho.dylinker_command,
Dylib: macho.dylib_command,
EntryPoint: macho.entry_point_command,
+ MinVersion: macho.version_min_command,
+ SourceVersion: macho.source_version_command,
pub fn cmdsize(self: LoadCommand) u32 {
return switch (self) {
@@ -44,6 +48,8 @@ const LoadCommand = union(enum) {
.Dylinker => |x| x.cmdsize,
.Dylib => |x| x.cmdsize,
.EntryPoint => |x| x.cmdsize,
+ .MinVersion => |x| x.cmdsize,
+ .SourceVersion => |x| x.cmdsize,
};
}
@@ -57,6 +63,8 @@ const LoadCommand = union(enum) {
.Dylinker => |cmd| writeGeneric(cmd, file, offset),
.Dylib => |cmd| writeGeneric(cmd, file, offset),
.EntryPoint => |cmd| writeGeneric(cmd, file, offset),
+ .MinVersion => |cmd| writeGeneric(cmd, file, offset),
+ .SourceVersion => |cmd| writeGeneric(cmd, file, offset),
};
}
@@ -68,6 +76,10 @@ const LoadCommand = union(enum) {
base: File,
+/// Page size is dependent on the target cpu architecture.
+/// For x86_64 that's 4KB, whereas for aarch64, that's 16KB.
+page_size: u16,
+
/// Table of all load commands
load_commands: std.ArrayListUnmanaged(LoadCommand) = .{},
/// __PAGEZERO segment
@@ -96,6 +108,12 @@ function_starts_cmd_index: ?u16 = null,
/// Specifies offset wrt __TEXT segment start address to the main entry point
/// of the binary.
main_cmd_index: ?u16 = null,
+/// Minimum OS version
+version_min_cmd_index: ?u16 = null,
+/// Source version
+source_version_cmd_index: ?u16 = null,
+/// Code signature
+code_signature_cmd_index: ?u16 = null,
/// Table of all sections
sections: std.ArrayListUnmanaged(macho.section_64) = .{},
@@ -108,6 +126,9 @@ got_section_index: ?u16 = null,
entry_addr: ?u64 = null,
+// TODO move this into each Segment aggregator
+linkedit_segment_next_offset: ?u32 = null,
+
/// Table of all local symbols
/// Internally references string table for names (which are optional).
local_symbols: std.ArrayListUnmanaged(macho.nlist_64) = .{},
@@ -154,6 +175,22 @@ libsystem_cmd_dirty: bool = false,
text_block_free_list: std.ArrayListUnmanaged(*TextBlock) = .{},
/// Pointer to the last allocated text block
last_text_block: ?*TextBlock = null,
+/// A list of all PIE fixups required for this run of the linker.
+/// Warning, this is currently NOT thread-safe. See the TODO below.
+/// TODO Move this list inside `updateDecl` where it should be allocated
+/// prior to calling `generateSymbol`, and then immediately deallocated
+/// rather than sitting in the global scope.
+pie_fixups: std.ArrayListUnmanaged(PieFixup) = .{},
+
+pub const PieFixup = struct {
+ /// Target address we wanted to address in absolute terms.
+ address: u64,
+ /// Where in the byte stream we should perform the fixup.
+ start: usize,
+ /// The length of the byte stream. For x86_64, this will be
+ /// variable. For aarch64, it will be fixed at 4 bytes.
+ len: usize,
+};
/// `alloc_num / alloc_den` is the factor of padding when allocating.
const alloc_num = 4;
@@ -292,6 +329,7 @@ pub fn createEmpty(gpa: *Allocator, options: link.Options) !*MachO {
.allocator = gpa,
.file = null,
},
+ .page_size = if (options.target.cpu.arch == .aarch64) 0x4000 else 0x1000,
};
return self;
}
@@ -312,35 +350,14 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void {
const tracy = trace(@src());
defer tracy.end();
- // Unfortunately these have to be buffered and done at the end because MachO does not allow
- // mixing local, global and undefined symbols within a symbol table.
- try self.writeAllGlobalSymbols();
- try self.writeAllUndefSymbols();
-
- try self.writeStringTable();
-
switch (self.base.options.output_mode) {
.Exe => {
- // Write export trie.
- try self.writeExportTrie();
if (self.entry_addr) |addr| {
// Update LC_MAIN with entry offset.
const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment;
const main_cmd = &self.load_commands.items[self.main_cmd_index.?].EntryPoint;
main_cmd.entryoff = addr - text_segment.vmaddr;
}
- {
- // Update dynamic symbol table.
- const nlocals = @intCast(u32, self.local_symbols.items.len);
- const nglobals = @intCast(u32, self.global_symbols.items.len);
- const nundefs = @intCast(u32, self.undef_symbols.items.len);
- const dysymtab = &self.load_commands.items[self.dysymtab_cmd_index.?].Dysymtab;
- dysymtab.nlocalsym = nlocals;
- dysymtab.iextdefsym = nlocals;
- dysymtab.nextdefsym = nglobals;
- dysymtab.iundefsym = nlocals + nglobals;
- dysymtab.nundefsym = nundefs;
- }
if (self.dylinker_cmd_dirty) {
// Write path to dyld loader.
var off: usize = @sizeOf(macho.mach_header_64);
@@ -367,20 +384,22 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void {
try self.base.file.?.pwriteAll(mem.spanZ(LIB_SYSTEM_PATH), off);
self.libsystem_cmd_dirty = false;
}
+
+ try self.writeExportTrie();
+ try self.writeSymbolTable();
+ try self.writeStringTable();
+
+ // Preallocate space for the code signature.
+ // We need to do this at this stage so that we have the load commands with proper values
+ // written out to the file.
+ // The most important here is to have the correct vm and filesize of the __LINKEDIT segment
+ // where the code signature goes into.
+ try self.writeCodeSignaturePadding();
},
.Obj => {},
.Lib => return error.TODOImplementWritingLibFiles,
}
- {
- // Update symbol table.
- const nlocals = @intCast(u32, self.local_symbols.items.len);
- const nglobals = @intCast(u32, self.global_symbols.items.len);
- const nundefs = @intCast(u32, self.undef_symbols.items.len);
- const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab;
- symtab.nsyms = nlocals + nglobals + nundefs;
- }
-
if (self.cmd_table_dirty) {
try self.writeCmdHeaders();
try self.writeMachOHeader();
@@ -398,6 +417,11 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void {
assert(!self.cmd_table_dirty);
assert(!self.dylinker_cmd_dirty);
assert(!self.libsystem_cmd_dirty);
+
+ switch (self.base.options.output_mode) {
+ .Exe, .Lib => try self.writeCodeSignature(), // code signing always comes last
+ else => {},
+ }
}
fn linkWithLLD(self: *MachO, comp: *Compilation) !void {
@@ -823,6 +847,7 @@ fn darwinArchString(arch: std.Target.Cpu.Arch) []const u8 {
}
pub fn deinit(self: *MachO) void {
+ self.pie_fixups.deinit(self.base.allocator);
self.text_block_free_list.deinit(self.base.allocator);
self.offset_table.deinit(self.base.allocator);
self.offset_table_free_list.deinit(self.base.allocator);
@@ -955,7 +980,6 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {
log.debug("growing {} from 0x{x} to 0x{x}\n", .{ decl.name, symbol.n_value, vaddr });
if (vaddr != symbol.n_value) {
symbol.n_value = vaddr;
-
log.debug(" (writing new offset table entry)\n", .{});
self.offset_table.items[decl.link.macho.offset_table_index] = vaddr;
try self.writeOffsetTableEntry(decl.link.macho.offset_table_index);
@@ -968,8 +992,6 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {
symbol.n_type = macho.N_SECT;
symbol.n_sect = @intCast(u8, self.text_section_index.?) + 1;
symbol.n_desc = 0;
- // TODO this write could be avoided if no fields of the symbol were changed.
- try self.writeSymbol(decl.link.macho.local_sym_index);
} else {
const decl_name = mem.spanZ(decl.name);
const name_str_index = try self.makeString(decl_name);
@@ -985,15 +1007,32 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {
.n_value = addr,
};
self.offset_table.items[decl.link.macho.offset_table_index] = addr;
-
- try self.writeSymbol(decl.link.macho.local_sym_index);
try self.writeOffsetTableEntry(decl.link.macho.offset_table_index);
}
+ // Perform PIE fixups (if any)
+ const got_section = self.sections.items[self.got_section_index.?];
+ while (self.pie_fixups.popOrNull()) |fixup| {
+ const target_addr = fixup.address;
+ const this_addr = symbol.n_value + fixup.start;
+ switch (self.base.options.target.cpu.arch) {
+ .x86_64 => {
+ const displacement = @intCast(u32, target_addr - this_addr - fixup.len);
+ var placeholder = code_buffer.items[fixup.start + fixup.len - @sizeOf(u32) ..][0..@sizeOf(u32)];
+ mem.writeIntSliceLittle(u32, placeholder, displacement);
+ },
+ .aarch64 => {
+ const displacement = @intCast(u27, target_addr - this_addr);
+ var placeholder = code_buffer.items[fixup.start..][0..fixup.len];
+ mem.writeIntSliceLittle(u32, placeholder, aarch64.Instruction.b(@intCast(i28, displacement)).toU32());
+ },
+ else => unreachable, // unsupported target architecture
+ }
+ }
+
const text_section = self.sections.items[self.text_section_index.?];
const section_offset = symbol.n_value - text_section.addr;
const file_offset = text_section.offset + section_offset;
-
try self.base.file.?.pwriteAll(code, file_offset);
// Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated.
@@ -1127,7 +1166,8 @@ pub fn populateMissingMetadata(self: *MachO) !void {
}
if (self.text_segment_cmd_index == null) {
self.text_segment_cmd_index = @intCast(u16, self.load_commands.items.len);
- const prot = macho.VM_PROT_READ | macho.VM_PROT_EXECUTE;
+ const maxprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE | macho.VM_PROT_EXECUTE;
+ const initprot = macho.VM_PROT_READ | macho.VM_PROT_EXECUTE;
try self.load_commands.append(self.base.allocator, .{
.Segment = .{
.cmd = macho.LC_SEGMENT_64,
@@ -1137,8 +1177,8 @@ pub fn populateMissingMetadata(self: *MachO) !void {
.vmsize = 0,
.fileoff = 0,
.filesize = 0,
- .maxprot = prot,
- .initprot = prot,
+ .maxprot = maxprot,
+ .initprot = initprot,
.nsects = 0,
.flags = 0,
},
@@ -1148,11 +1188,10 @@ pub fn populateMissingMetadata(self: *MachO) !void {
if (self.text_section_index == null) {
self.text_section_index = @intCast(u16, self.sections.items.len);
const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment;
- text_segment.cmdsize += @sizeOf(macho.section_64);
- text_segment.nsects += 1;
- const file_size = mem.alignForwardGeneric(u64, self.base.options.program_code_size_hint, 0x1000);
- const off = @intCast(u32, self.findFreeSpace(file_size, 0x1000)); // TODO maybe findFreeSpace should return u32 directly?
+ const program_code_size_hint = self.base.options.program_code_size_hint;
+ const file_size = mem.alignForwardGeneric(u64, program_code_size_hint, self.page_size);
+ const off = @intCast(u32, self.findFreeSpace(file_size, self.page_size)); // TODO maybe findFreeSpace should return u32 directly?
const flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS;
log.debug("found __text section free space 0x{x} to 0x{x}\n", .{ off, off + file_size });
@@ -1163,7 +1202,7 @@ pub fn populateMissingMetadata(self: *MachO) !void {
.addr = text_segment.vmaddr + off,
.size = file_size,
.offset = off,
- .@"align" = 12, // 2^12 = 4096
+ .@"align" = if (self.base.options.target.cpu.arch == .aarch64) 2 else 0, // 2^2 for aarch64, 2^0 for x86_64
.reloff = 0,
.nreloc = 0,
.flags = flags,
@@ -1174,47 +1213,27 @@ pub fn populateMissingMetadata(self: *MachO) !void {
text_segment.vmsize = file_size + off; // We add off here since __TEXT segment includes everything prior to __text section.
text_segment.filesize = file_size + off;
- self.cmd_table_dirty = true;
- }
- if (self.data_segment_cmd_index == null) {
- self.data_segment_cmd_index = @intCast(u16, self.load_commands.items.len);
- const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment;
- const prot = macho.VM_PROT_READ | macho.VM_PROT_WRITE;
- try self.load_commands.append(self.base.allocator, .{
- .Segment = .{
- .cmd = macho.LC_SEGMENT_64,
- .cmdsize = @sizeOf(macho.segment_command_64),
- .segname = makeStaticString("__DATA"),
- .vmaddr = text_segment.vmaddr + text_segment.vmsize,
- .vmsize = 0,
- .fileoff = 0,
- .filesize = 0,
- .maxprot = prot,
- .initprot = prot,
- .nsects = 0,
- .flags = 0,
- },
- });
+ text_segment.cmdsize += @sizeOf(macho.section_64);
+ text_segment.nsects += 1;
self.cmd_table_dirty = true;
}
if (self.got_section_index == null) {
self.got_section_index = @intCast(u16, self.sections.items.len);
- const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
- data_segment.cmdsize += @sizeOf(macho.section_64);
- data_segment.nsects += 1;
+ const text_section = &self.sections.items[self.text_section_index.?];
const file_size = @sizeOf(u64) * self.base.options.symbol_count_hint;
- const off = @intCast(u32, self.findFreeSpace(file_size, 0x1000));
+ // TODO looking for free space should be done *within* a segment it belongs to
+ const off = @intCast(u32, text_section.offset + text_section.size);
log.debug("found __got section free space 0x{x} to 0x{x}\n", .{ off, off + file_size });
try self.sections.append(self.base.allocator, .{
.sectname = makeStaticString("__got"),
- .segname = makeStaticString("__DATA"),
- .addr = data_segment.vmaddr,
+ .segname = makeStaticString("__TEXT"),
+ .addr = text_section.addr + text_section.size,
.size = file_size,
.offset = off,
- .@"align" = 3, // 2^3 = 8
+ .@"align" = if (self.base.options.target.cpu.arch == .aarch64) 2 else 0,
.reloff = 0,
.nreloc = 0,
.flags = macho.S_REGULAR,
@@ -1223,31 +1242,36 @@ pub fn populateMissingMetadata(self: *MachO) !void {
.reserved3 = 0,
});
- const segment_size = mem.alignForwardGeneric(u64, file_size, 0x1000);
- data_segment.vmsize = segment_size;
- data_segment.filesize = segment_size;
- data_segment.fileoff = off;
+ const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment;
+ const added_size = mem.alignForwardGeneric(u64, file_size, self.page_size);
+ text_segment.vmsize += added_size;
+ text_segment.filesize += added_size;
+ text_segment.cmdsize += @sizeOf(macho.section_64);
+ text_segment.nsects += 1;
self.cmd_table_dirty = true;
}
if (self.linkedit_segment_cmd_index == null) {
self.linkedit_segment_cmd_index = @intCast(u16, self.load_commands.items.len);
- const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
- const prot = macho.VM_PROT_READ | macho.VM_PROT_WRITE | macho.VM_PROT_EXECUTE;
+ const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment;
+ const maxprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE | macho.VM_PROT_EXECUTE;
+ const initprot = macho.VM_PROT_READ;
+ const off = text_segment.fileoff + text_segment.filesize;
try self.load_commands.append(self.base.allocator, .{
.Segment = .{
.cmd = macho.LC_SEGMENT_64,
.cmdsize = @sizeOf(macho.segment_command_64),
.segname = makeStaticString("__LINKEDIT"),
- .vmaddr = data_segment.vmaddr + data_segment.vmsize,
+ .vmaddr = text_segment.vmaddr + text_segment.vmsize,
.vmsize = 0,
- .fileoff = 0,
+ .fileoff = off,
.filesize = 0,
- .maxprot = prot,
- .initprot = prot,
+ .maxprot = maxprot,
+ .initprot = initprot,
.nsects = 0,
.flags = 0,
},
});
+ self.linkedit_segment_next_offset = @intCast(u32, off);
self.cmd_table_dirty = true;
}
if (self.dyld_info_cmd_index == null) {
@@ -1329,8 +1353,8 @@ pub fn populateMissingMetadata(self: *MachO) !void {
self.libsystem_cmd_index = @intCast(u16, self.load_commands.items.len);
const cmdsize = mem.alignForwardGeneric(u64, @sizeOf(macho.dylib_command) + mem.lenZ(LIB_SYSTEM_PATH), @sizeOf(u64));
// TODO Find a way to work out runtime version from the OS version triple stored in std.Target.
- // In the meantime, we're gonna hardcode to the minimum compatibility version of 1.0.0.
- const min_version = 0x10000;
+ // In the meantime, we're gonna hardcode to the minimum compatibility version of 0.0.0.
+ const min_version = 0x0;
const dylib = .{
.name = @sizeOf(macho.dylib_command),
.timestamp = 2, // not sure why not simply 0; this is reverse engineered from Mach-O files
@@ -1359,47 +1383,46 @@ pub fn populateMissingMetadata(self: *MachO) !void {
});
self.cmd_table_dirty = true;
}
- {
- const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
- const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfo;
- if (dyld_info.export_off == 0) {
- const nsyms = self.base.options.symbol_count_hint;
- const file_size = @sizeOf(u64) * nsyms;
- const off = @intCast(u32, self.findFreeSpace(file_size, 0x1000));
- log.debug("found export trie free space 0x{x} to 0x{x}\n", .{ off, off + file_size });
- dyld_info.export_off = off;
- dyld_info.export_size = @intCast(u32, file_size);
-
- const segment_size = mem.alignForwardGeneric(u64, file_size, 0x1000);
- linkedit.vmsize += segment_size;
- linkedit.fileoff = off;
- }
+ if (self.version_min_cmd_index == null) {
+ self.version_min_cmd_index = @intCast(u16, self.load_commands.items.len);
+ const cmd: u32 = switch (self.base.options.target.os.tag) {
+ .macos => macho.LC_VERSION_MIN_MACOSX,
+ .ios => macho.LC_VERSION_MIN_IPHONEOS,
+ .tvos => macho.LC_VERSION_MIN_TVOS,
+ .watchos => macho.LC_VERSION_MIN_WATCHOS,
+ else => unreachable, // wrong OS
+ };
+ const ver = self.base.options.target.os.version_range.semver.min;
+ const version = ver.major << 16 | ver.minor << 8 | ver.patch;
+ try self.load_commands.append(self.base.allocator, .{
+ .MinVersion = .{
+ .cmd = cmd,
+ .cmdsize = @sizeOf(macho.version_min_command),
+ .version = version,
+ .sdk = version,
+ },
+ });
}
- {
- const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
- const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab;
- if (symtab.symoff == 0) {
- const nsyms = self.base.options.symbol_count_hint;
- const file_size = @sizeOf(macho.nlist_64) * nsyms;
- const off = @intCast(u32, self.findFreeSpace(file_size, 0x1000));
- log.debug("found symbol table free space 0x{x} to 0x{x}\n", .{ off, off + file_size });
- symtab.symoff = off;
- symtab.nsyms = @intCast(u32, nsyms);
-
- const segment_size = mem.alignForwardGeneric(u64, file_size, 0x1000);
- linkedit.vmsize += segment_size;
- }
- if (symtab.stroff == 0) {
- try self.string_table.append(self.base.allocator, 0);
- const file_size = @intCast(u32, self.string_table.items.len);
- const off = @intCast(u32, self.findFreeSpace(file_size, 0x1000));
- log.debug("found string table free space 0x{x} to 0x{x}\n", .{ off, off + file_size });
- symtab.stroff = off;
- symtab.strsize = file_size;
-
- const segment_size = mem.alignForwardGeneric(u64, file_size, 0x1000);
- linkedit.vmsize += segment_size;
- }
+ if (self.source_version_cmd_index == null) {
+ self.source_version_cmd_index = @intCast(u16, self.load_commands.items.len);
+ try self.load_commands.append(self.base.allocator, .{
+ .SourceVersion = .{
+ .cmd = macho.LC_SOURCE_VERSION,
+ .cmdsize = @sizeOf(macho.source_version_command),
+ .version = 0x0,
+ },
+ });
+ }
+ if (self.code_signature_cmd_index == null) {
+ self.code_signature_cmd_index = @intCast(u16, self.load_commands.items.len);
+ try self.load_commands.append(self.base.allocator, .{
+ .LinkeditData = .{
+ .cmd = macho.LC_CODE_SIGNATURE,
+ .cmdsize = @sizeOf(macho.linkedit_data_command),
+ .dataoff = 0,
+ .datasize = 0,
+ },
+ });
}
if (self.dyld_stub_binder_index == null) {
self.dyld_stub_binder_index = @intCast(u16, self.undef_symbols.items.len);
@@ -1626,47 +1649,121 @@ fn findFreeSpace(self: *MachO, object_size: u64, min_alignment: u16) u64 {
return start;
}
-fn writeSymbol(self: *MachO, index: usize) !void {
- const tracy = trace(@src());
- defer tracy.end();
-
- const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab;
- const sym = [1]macho.nlist_64{self.local_symbols.items[index]};
- const off = symtab.symoff + @sizeOf(macho.nlist_64) * index;
- log.debug("writing symbol {} at 0x{x}\n", .{ sym[0], off });
- try self.base.file.?.pwriteAll(mem.sliceAsBytes(sym[0..1]), off);
-}
-
fn writeOffsetTableEntry(self: *MachO, index: usize) !void {
const sect = &self.sections.items[self.got_section_index.?];
- const endian = self.base.options.target.cpu.arch.endian();
- var buf: [@sizeOf(u64)]u8 = undefined;
- mem.writeInt(u64, &buf, self.offset_table.items[index], endian);
const off = sect.offset + @sizeOf(u64) * index;
+ const vmaddr = sect.addr + @sizeOf(u64) * index;
+
+ var code: [8]u8 = undefined;
+ switch (self.base.options.target.cpu.arch) {
+ .x86_64 => {
+ const pos_symbol_off = @intCast(u31, vmaddr - self.offset_table.items[index] + 7);
+ const symbol_off = @bitCast(u32, @intCast(i32, pos_symbol_off) * -1);
+ // lea %rax, [rip - disp]
+ code[0] = 0x48;
+ code[1] = 0x8D;
+ code[2] = 0x5;
+ mem.writeIntLittle(u32, code[3..7], symbol_off);
+ // ret
+ code[7] = 0xC3;
+ },
+ .aarch64 => {
+ const pos_symbol_off = @intCast(u20, vmaddr - self.offset_table.items[index]);
+ const symbol_off = @intCast(i21, pos_symbol_off) * -1;
+ // adr x0, #-disp
+ mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.adr(.x0, symbol_off).toU32());
+ // ret x28
+ mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.ret(.x28).toU32());
+ },
+ else => unreachable, // unsupported target architecture
+ }
log.debug("writing offset table entry 0x{x} at 0x{x}\n", .{ self.offset_table.items[index], off });
- try self.base.file.?.pwriteAll(&buf, off);
+ try self.base.file.?.pwriteAll(&code, off);
}
-fn writeAllGlobalSymbols(self: *MachO) !void {
+fn writeSymbolTable(self: *MachO) !void {
+ // TODO workout how we can cache these so that we only overwrite symbols that were updated
const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab;
- const off = symtab.symoff + self.local_symbols.items.len * @sizeOf(macho.nlist_64);
- const file_size = self.global_symbols.items.len * @sizeOf(macho.nlist_64);
- log.debug("writing global symbols from 0x{x} to 0x{x}\n", .{ off, file_size + off });
- try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.global_symbols.items), off);
+
+ const locals_off = self.linkedit_segment_next_offset.?;
+ const locals_size = self.local_symbols.items.len * @sizeOf(macho.nlist_64);
+ log.debug("writing local symbols from 0x{x} to 0x{x}\n", .{ locals_off, locals_size + locals_off });
+ try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.local_symbols.items), locals_off);
+
+ const globals_off = locals_off + locals_size;
+ const globals_size = self.global_symbols.items.len * @sizeOf(macho.nlist_64);
+ log.debug("writing global symbols from 0x{x} to 0x{x}\n", .{ globals_off, globals_size + globals_off });
+ try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.global_symbols.items), globals_off);
+
+ const undefs_off = globals_off + globals_size;
+ const undefs_size = self.undef_symbols.items.len * @sizeOf(macho.nlist_64);
+ log.debug("writing undef symbols from 0x{x} to 0x{x}\n", .{ undefs_off, undefs_size + undefs_off });
+ try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.undef_symbols.items), undefs_off);
+
+ // Update symbol table.
+ const nlocals = @intCast(u32, self.local_symbols.items.len);
+ const nglobals = @intCast(u32, self.global_symbols.items.len);
+ const nundefs = @intCast(u32, self.undef_symbols.items.len);
+ symtab.symoff = self.linkedit_segment_next_offset.?;
+ symtab.nsyms = nlocals + nglobals + nundefs;
+ self.linkedit_segment_next_offset = symtab.symoff + symtab.nsyms * @sizeOf(macho.nlist_64);
+
+ // Update dynamic symbol table.
+ const dysymtab = &self.load_commands.items[self.dysymtab_cmd_index.?].Dysymtab;
+ dysymtab.nlocalsym = nlocals;
+ dysymtab.iextdefsym = nlocals;
+ dysymtab.nextdefsym = nglobals;
+ dysymtab.iundefsym = nlocals + nglobals;
+ dysymtab.nundefsym = nundefs;
+
+ // Advance size of __LINKEDIT segment
+ const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
+ linkedit.filesize += symtab.nsyms * @sizeOf(macho.nlist_64);
+ if (linkedit.vmsize < linkedit.filesize) {
+ linkedit.vmsize = mem.alignForwardGeneric(u64, linkedit.filesize, self.page_size);
+ }
+ self.cmd_table_dirty = true;
}
-fn writeAllUndefSymbols(self: *MachO) !void {
- const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab;
- const nlocals = self.local_symbols.items.len;
- const nglobals = self.global_symbols.items.len;
- const off = symtab.symoff + (nlocals + nglobals) * @sizeOf(macho.nlist_64);
- const file_size = self.undef_symbols.items.len * @sizeOf(macho.nlist_64);
- log.debug("writing undef symbols from 0x{x} to 0x{x}\n", .{ off, file_size + off });
- try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.undef_symbols.items), off);
+fn writeCodeSignaturePadding(self: *MachO) !void {
+ const code_sig_cmd = &self.load_commands.items[self.code_signature_cmd_index.?].LinkeditData;
+ const fileoff = self.linkedit_segment_next_offset.?;
+ const datasize: u32 = 0x1000; // TODO Calculate the expected size of the signature.
+ code_sig_cmd.dataoff = fileoff;
+ code_sig_cmd.datasize = datasize;
+
+ self.linkedit_segment_next_offset = fileoff + datasize;
+ // Advance size of __LINKEDIT segment
+ const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
+ linkedit.filesize += datasize;
+ if (linkedit.vmsize < linkedit.filesize) {
+ linkedit.vmsize = mem.alignForwardGeneric(u64, linkedit.filesize, self.page_size);
+ }
+ log.debug("writing code signature padding from 0x{x} to 0x{x}\n", .{ fileoff, fileoff + datasize });
+ // Pad out the space. We need to do this to calculate valid hashes for everything in the file
+ // except for code signature data.
+ try self.base.file.?.pwriteAll(&[_]u8{0}, fileoff + datasize - 1);
+}
+
+fn writeCodeSignature(self: *MachO) !void {
+ const code_sig_cmd = &self.load_commands.items[self.code_signature_cmd_index.?].LinkeditData;
+ var code_sig = CodeSignature.init(self.base.allocator);
+ defer code_sig.deinit();
+
+ try code_sig.calcAdhocSignature(self);
+
+ var buffer = try self.base.allocator.alloc(u8, code_sig.size());
+ defer self.base.allocator.free(buffer);
+
+ code_sig.write(buffer);
+
+ log.debug("writing code signature from 0x{x} to 0x{x}\n", .{ code_sig_cmd.dataoff, code_sig_cmd.dataoff + buffer.len });
+
+ try self.base.file.?.pwriteAll(buffer, code_sig_cmd.dataoff);
}
fn writeExportTrie(self: *MachO) !void {
- if (self.global_symbols.items.len == 0) return; // No exports, nothing to do.
+ if (self.global_symbols.items.len == 0) return;
var trie: Trie = .{};
defer trie.deinit(self.base.allocator);
@@ -1689,28 +1786,51 @@ fn writeExportTrie(self: *MachO) !void {
try trie.writeULEB128Mem(self.base.allocator, &buffer);
const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfo;
+ const export_size = @intCast(u32, mem.alignForward(buffer.items.len, @sizeOf(u64)));
+ dyld_info.export_off = self.linkedit_segment_next_offset.?;
+ dyld_info.export_size = export_size;
+
+ log.debug("writing export trie from 0x{x} to 0x{x}\n", .{ dyld_info.export_off, dyld_info.export_off + export_size });
+
+ if (export_size > buffer.items.len) {
+ // Pad out to align(8).
+ try self.base.file.?.pwriteAll(&[_]u8{0}, dyld_info.export_off + export_size);
+ }
try self.base.file.?.pwriteAll(buffer.items, dyld_info.export_off);
+
+ self.linkedit_segment_next_offset = dyld_info.export_off + dyld_info.export_size;
+ // Advance size of __LINKEDIT segment
+ const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
+ linkedit.filesize += dyld_info.export_size;
+ if (linkedit.vmsize < linkedit.filesize) {
+ linkedit.vmsize = mem.alignForwardGeneric(u64, linkedit.filesize, self.page_size);
+ }
+ self.cmd_table_dirty = true;
}
fn writeStringTable(self: *MachO) !void {
const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab;
- const allocated_size = self.allocatedSize(symtab.stroff);
const needed_size = self.string_table.items.len;
- if (needed_size > allocated_size) {
- symtab.strsize = 0;
- symtab.stroff = @intCast(u32, self.findFreeSpace(needed_size, 1));
- }
- symtab.strsize = @intCast(u32, needed_size);
+ symtab.stroff = self.linkedit_segment_next_offset.?;
+ symtab.strsize = @intCast(u32, mem.alignForward(needed_size, @sizeOf(u64)));
log.debug("writing string table from 0x{x} to 0x{x}\n", .{ symtab.stroff, symtab.stroff + symtab.strsize });
+ if (symtab.strsize > needed_size) {
+ // Pad out to align(8);
+ try self.base.file.?.pwriteAll(&[_]u8{0}, symtab.stroff + symtab.strsize);
+ }
try self.base.file.?.pwriteAll(self.string_table.items, symtab.stroff);
- // TODO rework how we preallocate space for the entire __LINKEDIT segment instead of
- // doing dynamic updates like this.
+ self.linkedit_segment_next_offset = symtab.stroff + symtab.strsize;
+ // Advance size of __LINKEDIT segment
const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
- linkedit.filesize = symtab.stroff + symtab.strsize - linkedit.fileoff;
+ linkedit.filesize += symtab.strsize;
+ if (linkedit.vmsize < linkedit.filesize) {
+ linkedit.vmsize = mem.alignForwardGeneric(u64, linkedit.filesize, self.page_size);
+ }
+ self.cmd_table_dirty = true;
}
fn writeCmdHeaders(self: *MachO) !void {
@@ -1726,7 +1846,6 @@ fn writeCmdHeaders(self: *MachO) !void {
last_cmd_offset += cmd.cmdsize();
}
{
- // write __text section header
const off = if (self.text_segment_cmd_index) |text_segment_index| blk: {
var i: usize = 0;
var cmdsize: usize = @sizeOf(macho.mach_header_64) + @sizeOf(macho.segment_command_64);
@@ -1739,27 +1858,11 @@ fn writeCmdHeaders(self: *MachO) !void {
// only one, noname segment to append this section header to.
return error.TODOImplementWritingObjFiles;
};
- const idx = self.text_section_index.?;
- log.debug("writing text section header at 0x{x}\n", .{off});
- try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.sections.items[idx .. idx + 1]), off);
- }
- {
- // write __got section header
- const off = if (self.data_segment_cmd_index) |data_segment_index| blk: {
- var i: usize = 0;
- var cmdsize: usize = @sizeOf(macho.mach_header_64) + @sizeOf(macho.segment_command_64);
- while (i < data_segment_index) : (i += 1) {
- cmdsize += self.load_commands.items[i].cmdsize();
- }
- break :blk cmdsize;
- } else {
- // If we've landed in here, we are building a MachO object file, so we have
- // only one, noname segment to append this section header to.
- return error.TODOImplementWritingObjFiles;
- };
- const idx = self.got_section_index.?;
- log.debug("writing got section header at 0x{x}\n", .{off});
- try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.sections.items[idx .. idx + 1]), off);
+ // write sections belonging to __TEXT segment
+ // TODO section indices should belong to each Segment, and we should iterate dynamically.
+ const id = self.text_section_index.?;
+ log.debug("writing __TEXT section headers at 0x{x}\n", .{off});
+ try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.sections.items[id .. id + 2]), off);
}
}
@@ -1807,8 +1910,14 @@ fn writeMachOHeader(self: *MachO) !void {
hdr.sizeofcmds = sizeofcmds;
- // TODO should these be set to something else?
- hdr.flags = 0;
+ switch (self.base.options.output_mode) {
+ .Exe => {
+ hdr.flags = macho.MH_NOUNDEFS | macho.MH_DYLDLINK | macho.MH_PIE;
+ },
+ else => {
+ hdr.flags = 0;
+ },
+ }
hdr.reserved = 0;
log.debug("writing Mach-O header {}\n", .{hdr});
diff --git a/src/link/MachO/CodeSignature.zig b/src/link/MachO/CodeSignature.zig
new file mode 100644
index 0000000000..6262315eb8
--- /dev/null
+++ b/src/link/MachO/CodeSignature.zig
@@ -0,0 +1,182 @@
+const CodeSignature = @This();
+
+const std = @import("std");
+const assert = std.debug.assert;
+const log = std.log.scoped(.link);
+const macho = std.macho;
+const mem = std.mem;
+const testing = std.testing;
+const Allocator = mem.Allocator;
+const Sha256 = std.crypto.hash.sha2.Sha256;
+
+const MachO = @import("../MachO.zig");
+
+const hash_size: u8 = 32;
+const page_size: u16 = 0x1000;
+
+const CodeDirectory = struct {
+ inner: macho.CodeDirectory,
+ data: std.ArrayListUnmanaged(u8) = .{},
+
+ fn size(self: CodeDirectory) u32 {
+ return self.inner.length;
+ }
+
+ fn write(self: CodeDirectory, buffer: []u8) void {
+ assert(buffer.len >= self.inner.length);
+
+ mem.writeIntBig(u32, buffer[0..4], self.inner.magic);
+ mem.writeIntBig(u32, buffer[4..8], self.inner.length);
+ mem.writeIntBig(u32, buffer[8..12], self.inner.version);
+ mem.writeIntBig(u32, buffer[12..16], self.inner.flags);
+ mem.writeIntBig(u32, buffer[16..20], self.inner.hashOffset);
+ mem.writeIntBig(u32, buffer[20..24], self.inner.identOffset);
+ mem.writeIntBig(u32, buffer[24..28], self.inner.nSpecialSlots);
+ mem.writeIntBig(u32, buffer[28..32], self.inner.nCodeSlots);
+ mem.writeIntBig(u32, buffer[32..36], self.inner.codeLimit);
+ buffer[36] = self.inner.hashSize;
+ buffer[37] = self.inner.hashType;
+ buffer[38] = self.inner.platform;
+ buffer[39] = self.inner.pageSize;
+ mem.writeIntBig(u32, buffer[40..44], self.inner.spare2);
+ mem.writeIntBig(u32, buffer[44..48], self.inner.scatterOffset);
+ mem.writeIntBig(u32, buffer[48..52], self.inner.teamOffset);
+ mem.writeIntBig(u32, buffer[52..56], self.inner.spare3);
+ mem.writeIntBig(u64, buffer[56..64], self.inner.codeLimit64);
+ mem.writeIntBig(u64, buffer[64..72], self.inner.execSegBase);
+ mem.writeIntBig(u64, buffer[72..80], self.inner.execSegLimit);
+ mem.writeIntBig(u64, buffer[80..88], self.inner.execSegFlags);
+
+ mem.copy(u8, buffer[88..], self.data.items);
+ }
+};
+
+alloc: *Allocator,
+inner: macho.SuperBlob = .{
+ .magic = macho.CSMAGIC_EMBEDDED_SIGNATURE,
+ .length = @sizeOf(macho.SuperBlob),
+ .count = 0,
+},
+cdir: ?CodeDirectory = null,
+
+pub fn init(alloc: *Allocator) CodeSignature {
+ return .{
+ .alloc = alloc,
+ };
+}
+
+pub fn calcAdhocSignature(self: *CodeSignature, bin_file: *const MachO) !void {
+ const text_segment = bin_file.load_commands.items[bin_file.text_segment_cmd_index.?].Segment;
+ const code_sig_cmd = bin_file.load_commands.items[bin_file.code_signature_cmd_index.?].LinkeditData;
+
+ const execSegBase: u64 = text_segment.fileoff;
+ const execSegLimit: u64 = text_segment.filesize;
+ const execSegFlags: u64 = if (bin_file.base.options.output_mode == .Exe) macho.CS_EXECSEG_MAIN_BINARY else 0;
+ const file_size = code_sig_cmd.dataoff;
+ var cdir = CodeDirectory{
+ .inner = .{
+ .magic = macho.CSMAGIC_CODEDIRECTORY,
+ .length = @sizeOf(macho.CodeDirectory),
+ .version = macho.CS_SUPPORTSEXECSEG,
+ .flags = macho.CS_ADHOC,
+ .hashOffset = 0,
+ .identOffset = 0,
+ .nSpecialSlots = 0,
+ .nCodeSlots = 0,
+ .codeLimit = @intCast(u32, file_size),
+ .hashSize = hash_size,
+ .hashType = macho.CS_HASHTYPE_SHA256,
+ .platform = 0,
+ .pageSize = @truncate(u8, std.math.log2(page_size)),
+ .spare2 = 0,
+ .scatterOffset = 0,
+ .teamOffset = 0,
+ .spare3 = 0,
+ .codeLimit64 = 0,
+ .execSegBase = execSegBase,
+ .execSegLimit = execSegLimit,
+ .execSegFlags = execSegFlags,
+ },
+ };
+
+ const total_pages = mem.alignForward(file_size, page_size) / page_size;
+
+ var hash: [hash_size]u8 = undefined;
+ var buffer = try bin_file.base.allocator.alloc(u8, page_size);
+ defer bin_file.base.allocator.free(buffer);
+ const macho_file = bin_file.base.file.?;
+
+ const id = bin_file.base.options.emit.?.sub_path;
+ try cdir.data.ensureCapacity(self.alloc, total_pages * hash_size + id.len + 1);
+
+ // 1. Save the identifier and update offsets
+ cdir.inner.identOffset = cdir.inner.length;
+ cdir.data.appendSliceAssumeCapacity(id);
+ cdir.data.appendAssumeCapacity(0);
+
+ // 2. Calculate hash for each page (in file) and write it to the buffer
+ // TODO figure out how we can cache several hashes since we won't update
+ // every page during incremental linking
+ cdir.inner.hashOffset = cdir.inner.identOffset + @intCast(u32, id.len) + 1;
+ var i: usize = 0;
+ while (i < total_pages) : (i += 1) {
+ const fstart = i * page_size;
+ const fsize = if (fstart + page_size > file_size) file_size - fstart else page_size;
+ const len = try macho_file.preadAll(buffer, fstart);
+ assert(fsize <= len);
+
+ Sha256.hash(buffer[0..fsize], &hash, .{});
+
+ cdir.data.appendSliceAssumeCapacity(hash[0..]);
+ cdir.inner.nCodeSlots += 1;
+ }
+
+ // 3. Update CodeDirectory length
+ cdir.inner.length += @intCast(u32, cdir.data.items.len);
+
+ self.inner.length += @sizeOf(macho.BlobIndex) + cdir.size();
+ self.inner.count = 1;
+ self.cdir = cdir;
+}
+
+pub fn size(self: CodeSignature) u32 {
+ return self.inner.length;
+}
+
+pub fn write(self: CodeSignature, buffer: []u8) void {
+ assert(buffer.len >= self.inner.length);
+ self.writeHeader(buffer);
+ const offset: u32 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex);
+ writeBlobIndex(macho.CSSLOT_CODEDIRECTORY, offset, buffer[@sizeOf(macho.SuperBlob)..]);
+ self.cdir.?.write(buffer[offset..]);
+}
+
+pub fn deinit(self: *CodeSignature) void {
+ if (self.cdir) |*cdir| {
+ cdir.data.deinit(self.alloc);
+ }
+}
+
+fn writeHeader(self: CodeSignature, buffer: []u8) void {
+ assert(buffer.len >= @sizeOf(macho.SuperBlob));
+ mem.writeIntBig(u32, buffer[0..4], self.inner.magic);
+ mem.writeIntBig(u32, buffer[4..8], self.inner.length);
+ mem.writeIntBig(u32, buffer[8..12], self.inner.count);
+}
+
+fn writeBlobIndex(tt: u32, offset: u32, buffer: []u8) void {
+ assert(buffer.len >= @sizeOf(macho.BlobIndex));
+ mem.writeIntBig(u32, buffer[0..4], tt);
+ mem.writeIntBig(u32, buffer[4..8], offset);
+}
+
+test "CodeSignature header" {
+ var code_sig = CodeSignature.init(testing.allocator);
+ defer code_sig.deinit();
+
+ var buffer: [@sizeOf(macho.SuperBlob)]u8 = undefined;
+ code_sig.writeHeader(buffer[0..]);
+
+ const expected = &[_]u8{ 0xfa, 0xde, 0x0c, 0xc0, 0x0, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0x0 };
+ testing.expect(mem.eql(u8, expected[0..], buffer[0..]));
+}
diff --git a/src/main.zig b/src/main.zig
index 595faed88f..8de94e1abd 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -1727,6 +1727,9 @@ fn buildOutputType(
error.SemanticAnalyzeFail => process.exit(1),
else => |e| return e,
};
+ if (output_mode == .Exe) {
+ try comp.makeBinFileExecutable();
+ }
if (build_options.is_stage1 and comp.stage1_lock != null and watch) {
warn("--watch is not recommended with the stage1 backend; it leaks memory and is not capable of incremental compilation", .{});