authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2020-09-29 18:42:50+02:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2020-10-04 15:31:47+02:00
loga927f242019f22bda95fb4a74020966ac2bdb54e
tree79662c18b6dc20b2b923cf9d507c2a698467f55a
parent57a81bb5596f7487d728cf9a91013347e368912b

Generate more MachO exe boilerplate

* Convert draft to generate all relevant segments and sections in right places * Do not prealloc space in text blocks until we can NOP * Write out LC_LOAD_DYLINKER command * Add LC_LOAD_DYLIB command in order to specify to load libSystem * Redo update decl exports (similar to Elf globals, globals need to be contiguous in memory)

2 files changed, 355 insertions(+), 152 deletions(-)

lib/std/macho.zig+38
...@@ -1257,3 +1257,41 @@ pub const reloc_type_x86_64 = packed enum(u4) {...@@ -1257,3 +1257,41 @@ pub const reloc_type_x86_64 = packed enum(u4) {
1257 /// for thread local variables1257 /// for thread local variables
1258 X86_64_RELOC_TLV,1258 X86_64_RELOC_TLV,
1259};1259};
1260
1261/// This symbol is a reference to an external non-lazy (data) symbol.
1262pub const REFERENCE_FLAG_UNDEFINED_NON_LAZY: u16 = 0x0;
1263
1264/// This symbol is a reference to an external lazy symbol—that is, to a function call.
1265pub const REFERENCE_FLAG_UNDEFINED_LAZY: u16 = 0x1;
1266
1267/// This symbol is defined in this module.
1268pub const REFERENCE_FLAG_DEFINED: u16 = 0x2;
1269
1270/// This symbol is defined in this module and is visible only to modules within this shared library.
1271pub const REFERENCE_FLAG_PRIVATE_DEFINED: u16 = 3;
1272
1273/// This symbol is defined in another module in this file, is a non-lazy (data) symbol, and is visible
1274/// only to modules within this shared library.
1275pub const REFERENCE_FLAG_PRIVATE_UNDEFINED_NON_LAZY: u16 = 4;
1276
1277/// This symbol is defined in another module in this file, is a lazy (function) symbol, and is visible
1278/// only to modules within this shared library.
1279pub const REFERENCE_FLAG_PRIVATE_UNDEFINED_LAZY: u16 = 5;
1280
1281/// Must be set for any defined symbol that is referenced by dynamic-loader APIs (such as dlsym and
1282/// NSLookupSymbolInImage) and not ordinary undefined symbol references. The strip tool uses this bit
1283/// to avoid removing symbols that must exist: If the symbol has this bit set, strip does not strip it.
1284pub const REFERENCED_DYNAMICALLY: u16 = 0x10;
1285
1286/// Used by the dynamic linker at runtime. Do not set this bit.
1287pub const N_DESC_DISCARDED: u16 = 0x20;
1288
1289/// Indicates that this symbol is a weak reference. If the dynamic linker cannot find a definition
1290/// for this symbol, it sets the address of this symbol to 0. The static linker sets this symbol given
1291/// the appropriate weak-linking flags.
1292pub const N_WEAK_REF: u16 = 0x40;
1293
1294/// Indicates that this symbol is a weak definition. If the static linker or the dynamic linker finds
1295/// another (non-weak) definition for this symbol, the weak definition is ignored. Only symbols in a
1296/// coalesced section (page 23) can be marked as a weak definition.
1297pub const N_WEAK_DEF: u16 = 0x80;
src/link/MachO.zig+317-152
...@@ -76,7 +76,6 @@ text_segment_cmd_index: ?u16 = null,...@@ -76,7 +76,6 @@ text_segment_cmd_index: ?u16 = null,
76data_segment_cmd_index: ?u16 = null,76data_segment_cmd_index: ?u16 = null,
77/// __LINKEDIT segment77/// __LINKEDIT segment
78linkedit_segment_cmd_index: ?u16 = null,78linkedit_segment_cmd_index: ?u16 = null,
79segment_cmd_index: ?u16 = null,
80/// Dyld info79/// Dyld info
81dyld_info_cmd_index: ?u16 = null,80dyld_info_cmd_index: ?u16 = null,
82/// Symbol table81/// Symbol table
...@@ -107,9 +106,10 @@ got_section_index: ?u16 = null,...@@ -107,9 +106,10 @@ got_section_index: ?u16 = null,
107106
108entry_addr: ?u64 = null,107entry_addr: ?u64 = null,
109108
110/// Table of all symbols used.109/// Table of all local symbols used.
111/// Internally references string table for names (which are optional).110/// Internally references string table for names (which are optional).
112symbol_table: std.ArrayListUnmanaged(macho.nlist_64) = .{},111local_symbols: std.ArrayListUnmanaged(macho.nlist_64) = .{},
112global_symbols: std.ArrayListUnmanaged(macho.nlist_64) = .{},
113113
114/// Table of symbol names aka the string table.114/// Table of symbol names aka the string table.
115string_table: std.ArrayListUnmanaged(u8) = .{},115string_table: std.ArrayListUnmanaged(u8) = .{},
...@@ -227,87 +227,82 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void {...@@ -227,87 +227,82 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void {
227 defer tracy.end();227 defer tracy.end();
228228
229 switch (self.base.options.output_mode) {229 switch (self.base.options.output_mode) {
230 .Exe => {230 .Exe => {},
231 var last_cmd_offset: usize = @sizeOf(macho.mach_header_64);231 .Obj => return error.TODOImplementWritingObjFiles,
232 {232 .Lib => return error.TODOImplementWritingLibFiles,
233 // Specify path to dynamic linker dyld233 }
234 const cmdsize = commandSize(@sizeOf(macho.dylinker_command) + mem.lenZ(DEFAULT_DYLD_PATH));
235 const load_dylinker = [1]macho.dylinker_command{
236 .{
237 .cmd = macho.LC_LOAD_DYLINKER,
238 .cmdsize = cmdsize,
239 .name = @sizeOf(macho.dylinker_command),
240 },
241 };
242
243 try self.base.file.?.pwriteAll(mem.sliceAsBytes(load_dylinker[0..1]), last_cmd_offset);
244
245 const file_offset = last_cmd_offset + @sizeOf(macho.dylinker_command);
246 try self.addPadding(cmdsize - @sizeOf(macho.dylinker_command), file_offset);
247
248 try self.base.file.?.pwriteAll(mem.spanZ(DEFAULT_DYLD_PATH), file_offset);
249 last_cmd_offset += cmdsize;
250 }
251234
252 {235 // Unfortunately these have to be buffered and done at the end because ELF does not allow
253 // Link against libSystem236 // mixing local and global symbols within a symbol table.
254 const cmdsize = commandSize(@sizeOf(macho.dylib_command) + mem.lenZ(LIB_SYSTEM_PATH));237 try self.writeAllGlobalSymbols();
255 // TODO Find a way to work out runtime version from the OS version triple stored in std.Target.
256 // In the meantime, we're gonna hardcode to the minimum compatibility version of 1.0.0.
257 const min_version = 0x10000;
258 const dylib = .{
259 .name = @sizeOf(macho.dylib_command),
260 .timestamp = 2, // not sure why not simply 0; this is reverse engineered from Mach-O files
261 .current_version = min_version,
262 .compatibility_version = min_version,
263 };
264 const load_dylib = [1]macho.dylib_command{
265 .{
266 .cmd = macho.LC_LOAD_DYLIB,
267 .cmdsize = cmdsize,
268 .dylib = dylib,
269 },
270 };
271
272 try self.base.file.?.pwriteAll(mem.sliceAsBytes(load_dylib[0..1]), last_cmd_offset);
273
274 const file_offset = last_cmd_offset + @sizeOf(macho.dylib_command);
275 try self.addPadding(cmdsize - @sizeOf(macho.dylib_command), file_offset);
276
277 try self.base.file.?.pwriteAll(mem.spanZ(LIB_SYSTEM_PATH), file_offset);
278 last_cmd_offset += cmdsize;
279 }
280 },
281 .Obj => {
282 {
283 const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab;
284 symtab.nsyms = @intCast(u32, self.symbol_table.items.len);
285 const allocated_size = self.allocatedSize(symtab.stroff);
286 const needed_size = self.string_table.items.len;
287 log.debug("allocated_size = 0x{x}, needed_size = 0x{x}\n", .{ allocated_size, needed_size });
288
289 if (needed_size > allocated_size) {
290 symtab.strsize = 0;
291 symtab.stroff = @intCast(u32, self.findFreeSpace(needed_size, 1));
292 }
293 symtab.strsize = @intCast(u32, needed_size);
294238
295 log.debug("writing string table from 0x{x} to 0x{x}\n", .{ symtab.stroff, symtab.stroff + symtab.strsize });239 {
240 const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab;
241 symtab.nsyms = @intCast(u32, self.local_symbols.items.len + self.global_symbols.items.len);
242 const allocated_size = self.allocatedSize(symtab.stroff);
243 const needed_size = self.string_table.items.len;
244 log.debug("allocated_size = 0x{x}, needed_size = 0x{x}\n", .{ allocated_size, needed_size });
245
246 if (needed_size > allocated_size) {
247 symtab.strsize = 0;
248 symtab.stroff = @intCast(u32, self.findFreeSpace(needed_size, 1));
249 }
250 symtab.strsize = @intCast(u32, needed_size);
296251
297 try self.base.file.?.pwriteAll(self.string_table.items, symtab.stroff);252 log.debug("writing string table from 0x{x} to 0x{x}\n", .{ symtab.stroff, symtab.stroff + symtab.strsize });
298 }
299253
300 var last_cmd_offset: usize = @sizeOf(macho.mach_header_64);254 try self.base.file.?.pwriteAll(self.string_table.items, symtab.stroff);
301 for (self.load_commands.items) |cmd| {255 }
302 try cmd.write(&self.base.file.?, last_cmd_offset);256 {
303 last_cmd_offset += cmd.cmdsize();257 var last_cmd_offset: usize = @sizeOf(macho.mach_header_64);
304 }258 for (self.load_commands.items) |cmd| {
305 const off = @sizeOf(macho.mach_header_64) + @sizeOf(macho.segment_command_64);259 try cmd.write(&self.base.file.?, last_cmd_offset);
306 try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.sections.items), off);260 last_cmd_offset += cmd.cmdsize();
307 },261 }
308 .Lib => return error.TODOImplementWritingLibFiles,262 }
263 {
264 // write __text section
265 const off = @sizeOf(macho.mach_header_64) + @sizeOf(macho.segment_command_64) * 2;
266 log.debug("writing text section {} at 0x{x}\n", .{ self.sections.items[0..1], off });
267 try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.sections.items[0..1]), off);
268 }
269 {
270 // write __got section
271 const text = &self.load_commands.items[self.text_segment_cmd_index.?];
272 const off = @sizeOf(macho.mach_header_64) + @sizeOf(macho.segment_command_64) * 2 + text.cmdsize();
273 log.debug("writing got section {} at 0x{x}\n", .{ self.sections.items[1..2], off });
274 try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.sections.items[1..2]), off);
275 }
276 {
277 // write path to dyld
278 var off: usize = @sizeOf(macho.mach_header_64);
279 for (self.load_commands.items) |cmd| {
280 if (cmd == .Dylinker) break;
281 off += cmd.cmdsize();
282 }
283 const cmd = &self.load_commands.items[self.dylinker_cmd_index.?].Dylinker;
284 off += cmd.name;
285 const padding = cmd.cmdsize - @sizeOf(macho.dylinker_command);
286 log.debug("writing LC_LOAD_DYLINKER padding of size {} at 0x{x}\n", .{ padding, off });
287 try self.addPadding(padding, off);
288 log.debug("writing LC_LOAD_DYLINKER path to dyld at 0x{x}\n", .{off});
289 try self.base.file.?.pwriteAll(mem.spanZ(DEFAULT_DYLD_PATH), off);
290 }
291 {
292 // write path to libSystem
293 var off: usize = @sizeOf(macho.mach_header_64);
294 for (self.load_commands.items) |cmd| {
295 if (cmd == .Dylib) break;
296 off += cmd.cmdsize();
297 }
298 const cmd = &self.load_commands.items[self.libsystem_cmd_index.?].Dylib;
299 off += cmd.dylib.name;
300 const padding = cmd.cmdsize - @sizeOf(macho.dylib_command);
301 log.debug("writing LC_LOAD_DYLIB padding of size {} at 0x{x}\n", .{ padding, off });
302 try self.addPadding(padding, off);
303 log.debug("writing LC_LOAD_DYLIB path to libSystem at 0x{x}\n", .{off});
304 try self.base.file.?.pwriteAll(mem.spanZ(LIB_SYSTEM_PATH), off);
309 }305 }
310
311 if (self.entry_addr == null and self.base.options.output_mode == .Exe) {306 if (self.entry_addr == null and self.base.options.output_mode == .Exe) {
312 log.debug("flushing. no_entry_point_found = true\n", .{});307 log.debug("flushing. no_entry_point_found = true\n", .{});
313 self.error_flags.no_entry_point_found = true;308 self.error_flags.no_entry_point_found = true;
...@@ -699,7 +694,8 @@ fn darwinArchString(arch: std.Target.Cpu.Arch) []const u8 {...@@ -699,7 +694,8 @@ fn darwinArchString(arch: std.Target.Cpu.Arch) []const u8 {
699pub fn deinit(self: *MachO) void {694pub fn deinit(self: *MachO) void {
700 self.offset_table.deinit(self.base.allocator);695 self.offset_table.deinit(self.base.allocator);
701 self.string_table.deinit(self.base.allocator);696 self.string_table.deinit(self.base.allocator);
702 self.symbol_table.deinit(self.base.allocator);697 self.global_symbols.deinit(self.base.allocator);
698 self.local_symbols.deinit(self.base.allocator);
703 self.sections.deinit(self.base.allocator);699 self.sections.deinit(self.base.allocator);
704 self.load_commands.deinit(self.base.allocator);700 self.load_commands.deinit(self.base.allocator);
705}701}
...@@ -707,17 +703,17 @@ pub fn deinit(self: *MachO) void {...@@ -707,17 +703,17 @@ pub fn deinit(self: *MachO) void {
707pub fn allocateDeclIndexes(self: *MachO, decl: *Module.Decl) !void {703pub fn allocateDeclIndexes(self: *MachO, decl: *Module.Decl) !void {
708 if (decl.link.macho.symbol_table_index) |_| return;704 if (decl.link.macho.symbol_table_index) |_| return;
709705
710 try self.symbol_table.ensureCapacity(self.base.allocator, self.symbol_table.items.len + 1);706 try self.local_symbols.ensureCapacity(self.base.allocator, self.local_symbols.items.len + 1);
711 try self.offset_table.ensureCapacity(self.base.allocator, self.offset_table.items.len + 1);707 try self.offset_table.ensureCapacity(self.base.allocator, self.offset_table.items.len + 1);
712708
713 log.debug("allocating symbol index {} for {}\n", .{ self.symbol_table.items.len, decl.name });709 log.debug("allocating symbol index {} for {}\n", .{ self.local_symbols.items.len, decl.name });
714 decl.link.macho.symbol_table_index = @intCast(u32, self.symbol_table.items.len);710 decl.link.macho.symbol_table_index = @intCast(u32, self.local_symbols.items.len);
715 _ = self.symbol_table.addOneAssumeCapacity();711 _ = self.local_symbols.addOneAssumeCapacity();
716712
717 decl.link.macho.offset_table_index = @intCast(u32, self.offset_table.items.len);713 decl.link.macho.offset_table_index = @intCast(u32, self.offset_table.items.len);
718 _ = self.offset_table.addOneAssumeCapacity();714 _ = self.offset_table.addOneAssumeCapacity();
719715
720 self.symbol_table.items[decl.link.macho.symbol_table_index.?] = .{716 self.local_symbols.items[decl.link.macho.symbol_table_index.?] = .{
721 .n_strx = 0,717 .n_strx = 0,
722 .n_type = 0,718 .n_type = 0,
723 .n_sect = 0,719 .n_sect = 0,
...@@ -749,7 +745,7 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {...@@ -749,7 +745,7 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {
749 log.debug("generated code {}\n", .{code});745 log.debug("generated code {}\n", .{code});
750746
751 const required_alignment = typed_value.ty.abiAlignment(self.base.options.target);747 const required_alignment = typed_value.ty.abiAlignment(self.base.options.target);
752 const symbol = &self.symbol_table.items[decl.link.macho.symbol_table_index.?];748 const symbol = &self.local_symbols.items[decl.link.macho.symbol_table_index.?];
753749
754 const decl_name = mem.spanZ(decl.name);750 const decl_name = mem.spanZ(decl.name);
755 const name_str_index = try self.makeString(decl_name);751 const name_str_index = try self.makeString(decl_name);
...@@ -766,9 +762,6 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {...@@ -766,9 +762,6 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {
766 };762 };
767 self.offset_table.items[decl.link.macho.offset_table_index.?] = addr;763 self.offset_table.items[decl.link.macho.offset_table_index.?] = addr;
768764
769 // Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated.
770 const decl_exports = module.decl_exports.get(decl) orelse &[0]*Module.Export{};
771 try self.updateDeclExports(module, decl, decl_exports);
772 try self.writeSymbol(decl.link.macho.symbol_table_index.?);765 try self.writeSymbol(decl.link.macho.symbol_table_index.?);
773 try self.writeOffsetTableEntry(decl.link.macho.offset_table_index.?);766 try self.writeOffsetTableEntry(decl.link.macho.offset_table_index.?);
774767
...@@ -778,6 +771,10 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {...@@ -778,6 +771,10 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {
778 log.debug("file_offset 0x{x}\n", .{file_offset});771 log.debug("file_offset 0x{x}\n", .{file_offset});
779772
780 try self.base.file.?.pwriteAll(code, file_offset);773 try self.base.file.?.pwriteAll(code, file_offset);
774
775 // Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated.
776 const decl_exports = module.decl_exports.get(decl) orelse &[0]*Module.Export{};
777 try self.updateDeclExports(module, decl, decl_exports);
781}778}
782779
783pub fn updateDeclLineNumber(self: *MachO, module: *Module, decl: *const Module.Decl) !void {}780pub fn updateDeclLineNumber(self: *MachO, module: *Module, decl: *const Module.Decl) !void {}
...@@ -791,22 +788,72 @@ pub fn updateDeclExports(...@@ -791,22 +788,72 @@ pub fn updateDeclExports(
791 const tracy = trace(@src());788 const tracy = trace(@src());
792 defer tracy.end();789 defer tracy.end();
793790
791 try self.global_symbols.ensureCapacity(self.base.allocator, self.global_symbols.items.len + exports.len);
794 if (decl.link.macho.symbol_table_index == null) return;792 if (decl.link.macho.symbol_table_index == null) return;
795793 const decl_sym = &self.local_symbols.items[decl.link.macho.symbol_table_index.?];
796 const decl_sym = &self.symbol_table.items[decl.link.macho.symbol_table_index.?];794
797 // TODO implement795 for (exports) |exp| {
798 if (exports.len == 0) return;796 if (exp.options.section) |section_name| {
799797 if (!mem.eql(u8, section_name, "__text")) {
800 const exp = exports[0];798 try module.failed_exports.ensureCapacity(module.gpa, module.failed_exports.items().len + 1);
801 self.entry_addr = decl_sym.n_value;799 module.failed_exports.putAssumeCapacityNoClobber(
802 decl_sym.n_type |= macho.N_EXT;800 exp,
803 exp.link.sym_index = 0;801 try Module.ErrorMsg.create(self.base.allocator, 0, "Unimplemented: ExportOptions.section", .{}),
802 );
803 continue;
804 }
805 }
806 const n_desc = switch (exp.options.linkage) {
807 .Internal => macho.REFERENCE_FLAG_PRIVATE_DEFINED,
808 .Strong => blk: {
809 if (mem.eql(u8, exp.options.name, "_start")) {
810 self.entry_addr = decl_sym.n_value;
811 const cmd = &self.load_commands.items[self.main_cmd_index.?].EntryPoint;
812 cmd.entryoff = decl_sym.n_value;
813 }
814 break :blk macho.REFERENCE_FLAG_DEFINED;
815 },
816 .Weak => macho.N_WEAK_REF,
817 .LinkOnce => {
818 try module.failed_exports.ensureCapacity(module.gpa, module.failed_exports.items().len + 1);
819 module.failed_exports.putAssumeCapacityNoClobber(
820 exp,
821 try Module.ErrorMsg.create(self.base.allocator, 0, "Unimplemented: GlobalLinkage.LinkOnce", .{}),
822 );
823 continue;
824 },
825 };
826 const n_type = decl_sym.n_type | macho.N_EXT;
827 if (exp.link.sym_index) |i| {
828 const sym = &self.global_symbols.items[i];
829 sym.* = .{
830 .n_strx = try self.updateString(sym.n_strx, exp.options.name),
831 .n_type = n_type,
832 .n_sect = @intCast(u8, self.text_section_index.?) + 1,
833 .n_desc = n_desc,
834 .n_value = decl_sym.n_value,
835 };
836 } else {
837 const name_str_index = try self.makeString(exp.options.name);
838 _ = self.global_symbols.addOneAssumeCapacity();
839 const i = self.global_symbols.items.len - 1;
840 self.global_symbols.items[i] = .{
841 .n_strx = name_str_index,
842 .n_type = n_type,
843 .n_sect = @intCast(u8, self.text_section_index.?) + 1,
844 .n_desc = n_desc,
845 .n_value = decl_sym.n_value,
846 };
847
848 exp.link.sym_index = @intCast(u32, i);
849 }
850 }
804}851}
805852
806pub fn freeDecl(self: *MachO, decl: *Module.Decl) void {}853pub fn freeDecl(self: *MachO, decl: *Module.Decl) void {}
807854
808pub fn getDeclVAddr(self: *MachO, decl: *const Module.Decl) u64 {855pub fn getDeclVAddr(self: *MachO, decl: *const Module.Decl) u64 {
809 return self.symbol_table.items[decl.link.macho.symbol_table_index.?].n_value;856 return self.local_symbols.items[decl.link.macho.symbol_table_index.?].n_value;
810}857}
811858
812pub fn populateMissingMetadata(self: *MachO) !void {859pub fn populateMissingMetadata(self: *MachO) !void {
...@@ -818,7 +865,7 @@ pub fn populateMissingMetadata(self: *MachO) !void {...@@ -818,7 +865,7 @@ pub fn populateMissingMetadata(self: *MachO) !void {
818 .cmdsize = @sizeOf(macho.segment_command_64),865 .cmdsize = @sizeOf(macho.segment_command_64),
819 .segname = makeStaticString("__PAGEZERO"),866 .segname = makeStaticString("__PAGEZERO"),
820 .vmaddr = 0,867 .vmaddr = 0,
821 .vmsize = 0x1000, // size always set to 4GB868 .vmsize = 0x100000000, // size always set to 4GB
822 .fileoff = 0,869 .fileoff = 0,
823 .filesize = 0,870 .filesize = 0,
824 .maxprot = 0,871 .maxprot = 0,
...@@ -829,47 +876,34 @@ pub fn populateMissingMetadata(self: *MachO) !void {...@@ -829,47 +876,34 @@ pub fn populateMissingMetadata(self: *MachO) !void {
829 });876 });
830 self.cmd_table_dirty = true;877 self.cmd_table_dirty = true;
831 }878 }
832 if (self.segment_cmd_index == null) {879 if (self.text_segment_cmd_index == null) {
833 self.segment_cmd_index = @intCast(u16, self.load_commands.items.len);880 self.text_segment_cmd_index = @intCast(u16, self.load_commands.items.len);
881 const prot = macho.VM_PROT_READ | macho.VM_PROT_EXECUTE;
834 try self.load_commands.append(self.base.allocator, .{882 try self.load_commands.append(self.base.allocator, .{
835 .Segment = .{883 .Segment = .{
836 .cmd = macho.LC_SEGMENT_64,884 .cmd = macho.LC_SEGMENT_64,
837 .cmdsize = @sizeOf(macho.segment_command_64),885 .cmdsize = @sizeOf(macho.segment_command_64),
838 .segname = makeStaticString(""),886 .segname = makeStaticString("__TEXT"),
839 .vmaddr = 0,887 .vmaddr = 0x100000000, // always starts at 4GB
840 .vmsize = 0,888 .vmsize = 0,
841 .fileoff = 0,889 .fileoff = 0,
842 .filesize = 0,890 .filesize = 0,
843 .maxprot = 0,891 .maxprot = prot,
844 .initprot = 0,892 .initprot = prot,
845 .nsects = 0,893 .nsects = 0,
846 .flags = 0,894 .flags = 0,
847 },895 },
848 });896 });
849 self.cmd_table_dirty = true;897 self.cmd_table_dirty = true;
850 }898 }
851 if (self.symtab_cmd_index == null) {
852 self.symtab_cmd_index = @intCast(u16, self.load_commands.items.len);
853 try self.load_commands.append(self.base.allocator, .{
854 .Symtab = .{
855 .cmd = macho.LC_SYMTAB,
856 .cmdsize = @sizeOf(macho.symtab_command),
857 .symoff = 0,
858 .nsyms = 0,
859 .stroff = 0,
860 .strsize = 0,
861 },
862 });
863 self.cmd_table_dirty = true;
864 }
865 if (self.text_section_index == null) {899 if (self.text_section_index == null) {
866 self.text_section_index = @intCast(u16, self.sections.items.len);900 self.text_section_index = @intCast(u16, self.sections.items.len);
867 const segment = &self.load_commands.items[self.segment_cmd_index.?].Segment;901 const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment;
868 segment.cmdsize += @sizeOf(macho.section_64);902 text_segment.cmdsize += @sizeOf(macho.section_64);
869 segment.nsects += 1;903 text_segment.nsects += 1;
870904
871 const file_size = self.base.options.program_code_size_hint;905 const file_size = self.base.options.program_code_size_hint;
872 const off = @intCast(u32, self.findFreeSpace(file_size, 64));906 const off = @intCast(u32, self.findFreeSpace(file_size, 0x1000)); // TODO maybe findFreeSpace should return u32 directly?
873 const flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS;907 const flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS;
874908
875 log.debug("found __text section free space 0x{x} to 0x{x}\n", .{ off, off + file_size });909 log.debug("found __text section free space 0x{x} to 0x{x}\n", .{ off, off + file_size });
...@@ -877,7 +911,7 @@ pub fn populateMissingMetadata(self: *MachO) !void {...@@ -877,7 +911,7 @@ pub fn populateMissingMetadata(self: *MachO) !void {
877 try self.sections.append(self.base.allocator, .{911 try self.sections.append(self.base.allocator, .{
878 .sectname = makeStaticString("__text"),912 .sectname = makeStaticString("__text"),
879 .segname = makeStaticString("__TEXT"),913 .segname = makeStaticString("__TEXT"),
880 .addr = 0,914 .addr = text_segment.vmaddr + off,
881 .size = file_size,915 .size = file_size,
882 .offset = off,916 .offset = off,
883 .@"align" = 12,917 .@"align" = 12,
...@@ -889,32 +923,48 @@ pub fn populateMissingMetadata(self: *MachO) !void {...@@ -889,32 +923,48 @@ pub fn populateMissingMetadata(self: *MachO) !void {
889 .reserved3 = 0,923 .reserved3 = 0,
890 });924 });
891925
892 segment.vmsize += file_size;926 text_segment.vmsize = file_size + off;
893 segment.filesize += file_size;927 text_segment.filesize = file_size + off;
894 segment.fileoff = off;
895928
896 log.debug("initial text section {}\n", .{self.sections.items[self.text_section_index.?]});929 log.debug("initial text section {}\n", .{self.sections.items[self.text_section_index.?]});
897 log.debug("update segment {}\n", .{segment});930 log.debug("updated text segment {}\n", .{text_segment});
931 }
932 if (self.data_segment_cmd_index == null) {
933 self.data_segment_cmd_index = @intCast(u16, self.load_commands.items.len);
934 const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment;
935 const prot = macho.VM_PROT_READ | macho.VM_PROT_WRITE;
936 try self.load_commands.append(self.base.allocator, .{
937 .Segment = .{
938 .cmd = macho.LC_SEGMENT_64,
939 .cmdsize = @sizeOf(macho.segment_command_64),
940 .segname = makeStaticString("__DATA"),
941 .vmaddr = text_segment.vmaddr + text_segment.vmsize, // TODO this should be found when running findFreeSpace
942 .vmsize = 0,
943 .fileoff = 0,
944 .filesize = 0,
945 .maxprot = prot,
946 .initprot = prot,
947 .nsects = 0,
948 .flags = 0,
949 },
950 });
951 self.cmd_table_dirty = true;
898 }952 }
899 if (self.got_section_index == null) {953 if (self.got_section_index == null) {
900 self.got_section_index = @intCast(u16, self.sections.items.len);954 self.got_section_index = @intCast(u16, self.sections.items.len);
901 const segment = &self.load_commands.items[self.segment_cmd_index.?].Segment;955 const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
902 const text_sect = &self.sections.items[self.text_section_index.?];956 data_segment.cmdsize += @sizeOf(macho.section_64);
903 segment.cmdsize += @sizeOf(macho.section_64);957 data_segment.nsects += 1;
904 segment.nsects += 1;
905958
906 const p_align = @sizeOf(u64);959 const file_size = @sizeOf(u64) * self.base.options.symbol_count_hint;
907 const file_size = p_align * self.base.options.symbol_count_hint;960 const off = @intCast(u32, self.findFreeSpace(file_size, 0x1000));
908 const off = @intCast(u32, self.findFreeSpace(file_size, p_align));
909961
910 log.debug("found __got section free space 0x{x} to 0x{x}\n", .{ off, off + file_size });962 log.debug("found __got section free space 0x{x} to 0x{x}\n", .{ off, off + file_size });
911963
912 const padding_size = off - text_sect.offset - text_sect.size;
913
914 try self.sections.append(self.base.allocator, .{964 try self.sections.append(self.base.allocator, .{
915 .sectname = makeStaticString("__got"),965 .sectname = makeStaticString("__got"),
916 .segname = makeStaticString("__DATA"),966 .segname = makeStaticString("__DATA"),
917 .addr = text_sect.addr + text_sect.size + padding_size,967 .addr = data_segment.vmaddr,
918 .size = file_size,968 .size = file_size,
919 .offset = off,969 .offset = off,
920 .@"align" = 3,970 .@"align" = 3,
...@@ -926,46 +976,140 @@ pub fn populateMissingMetadata(self: *MachO) !void {...@@ -926,46 +976,140 @@ pub fn populateMissingMetadata(self: *MachO) !void {
926 .reserved3 = 0,976 .reserved3 = 0,
927 });977 });
928978
929 segment.vmsize += file_size + padding_size;979 data_segment.vmsize = file_size;
930 segment.filesize += file_size + padding_size;980 data_segment.filesize = file_size;
981 data_segment.fileoff = off;
931982
932 log.debug("initial got section {}\n", .{self.sections.items[self.got_section_index.?]});983 log.debug("initial got section {}\n", .{self.sections.items[self.got_section_index.?]});
933 log.debug("update segment {}\n", .{segment});984 log.debug("updated data segment {}\n", .{data_segment});
985 }
986 if (self.linkedit_segment_cmd_index == null) {
987 self.linkedit_segment_cmd_index = @intCast(u16, self.load_commands.items.len);
988 const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
989 const prot = macho.VM_PROT_READ | macho.VM_PROT_WRITE | macho.VM_PROT_EXECUTE;
990 try self.load_commands.append(self.base.allocator, .{
991 .Segment = .{
992 .cmd = macho.LC_SEGMENT_64,
993 .cmdsize = @sizeOf(macho.segment_command_64),
994 .segname = makeStaticString("__LINKEDIT"),
995 .vmaddr = data_segment.vmaddr + data_segment.vmsize, // TODO this should be found when running findFreeSpace
996 .vmsize = 0,
997 .fileoff = 0,
998 .filesize = 0,
999 .maxprot = prot,
1000 .initprot = prot,
1001 .nsects = 0,
1002 .flags = 0,
1003 },
1004 });
1005 self.cmd_table_dirty = true;
1006 }
1007 if (self.symtab_cmd_index == null) {
1008 self.symtab_cmd_index = @intCast(u16, self.load_commands.items.len);
1009 try self.load_commands.append(self.base.allocator, .{
1010 .Symtab = .{
1011 .cmd = macho.LC_SYMTAB,
1012 .cmdsize = @sizeOf(macho.symtab_command),
1013 .symoff = 0,
1014 .nsyms = 0,
1015 .stroff = 0,
1016 .strsize = 0,
1017 },
1018 });
1019 self.cmd_table_dirty = true;
1020 }
1021 if (self.dylinker_cmd_index == null) {
1022 self.dylinker_cmd_index = @intCast(u16, self.load_commands.items.len);
1023 const cmdsize = commandSize(@sizeOf(macho.dylinker_command) + mem.lenZ(DEFAULT_DYLD_PATH));
1024 try self.load_commands.append(self.base.allocator, .{
1025 .Dylinker = .{
1026 .cmd = macho.LC_LOAD_DYLINKER,
1027 .cmdsize = cmdsize,
1028 .name = @sizeOf(macho.dylinker_command),
1029 },
1030 });
1031 self.cmd_table_dirty = true;
1032 }
1033 if (self.libsystem_cmd_index == null) {
1034 self.libsystem_cmd_index = @intCast(u16, self.load_commands.items.len);
1035 const cmdsize = commandSize(@sizeOf(macho.dylib_command) + mem.lenZ(LIB_SYSTEM_PATH));
1036 // TODO Find a way to work out runtime version from the OS version triple stored in std.Target.
1037 // In the meantime, we're gonna hardcode to the minimum compatibility version of 1.0.0.
1038 const min_version = 0x10000;
1039 const dylib = .{
1040 .name = @sizeOf(macho.dylib_command),
1041 .timestamp = 2, // not sure why not simply 0; this is reverse engineered from Mach-O files
1042 .current_version = min_version,
1043 .compatibility_version = min_version,
1044 };
1045 try self.load_commands.append(self.base.allocator, .{
1046 .Dylib = .{
1047 .cmd = macho.LC_LOAD_DYLIB,
1048 .cmdsize = cmdsize,
1049 .dylib = dylib,
1050 },
1051 });
1052 self.cmd_table_dirty = true;
1053 }
1054 if (self.main_cmd_index == null) {
1055 self.main_cmd_index = @intCast(u16, self.load_commands.items.len);
1056 try self.load_commands.append(self.base.allocator, .{
1057 .EntryPoint = .{
1058 .cmd = macho.LC_MAIN,
1059 .cmdsize = @sizeOf(macho.entry_point_command),
1060 .entryoff = 0x0,
1061 .stacksize = 0,
1062 },
1063 });
1064 self.cmd_table_dirty = true;
934 }1065 }
935 {1066 {
1067 const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
936 const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab;1068 const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab;
937 if (symtab.symoff == 0) {1069 if (symtab.symoff == 0) {
938 const p_align = @sizeOf(macho.nlist_64);
939 const nsyms = self.base.options.symbol_count_hint;1070 const nsyms = self.base.options.symbol_count_hint;
940 const file_size = p_align * nsyms;1071 const file_size = @sizeOf(macho.nlist_64) * nsyms;
941 const off = @intCast(u32, self.findFreeSpace(file_size, p_align));1072 const off = @intCast(u32, self.findFreeSpace(file_size, 0x1000));
942 log.debug("found symbol table free space 0x{x} to 0x{x}\n", .{ off, off + file_size });1073 log.debug("found symbol table free space 0x{x} to 0x{x}\n", .{ off, off + file_size });
943 symtab.symoff = off;1074 symtab.symoff = off;
944 symtab.nsyms = @intCast(u32, nsyms);1075 symtab.nsyms = @intCast(u32, nsyms);
1076
1077 linkedit.vmsize += file_size;
1078 linkedit.fileoff = off;
1079 linkedit.filesize += file_size;
1080
1081 log.debug("updated linkedit segment {}\n", .{linkedit});
945 }1082 }
946 if (symtab.stroff == 0) {1083 if (symtab.stroff == 0) {
947 try self.string_table.append(self.base.allocator, 0);1084 try self.string_table.append(self.base.allocator, 0);
948 const file_size = @intCast(u32, self.string_table.items.len);1085 const file_size = @intCast(u32, self.string_table.items.len);
949 const off = @intCast(u32, self.findFreeSpace(file_size, 1));1086 const off = @intCast(u32, self.findFreeSpace(file_size, 0x1000));
950 log.debug("found string table free space 0x{x} to 0x{x}\n", .{ off, off + file_size });1087 log.debug("found string table free space 0x{x} to 0x{x}\n", .{ off, off + file_size });
951 symtab.stroff = off;1088 symtab.stroff = off;
952 symtab.strsize = file_size;1089 symtab.strsize = file_size;
1090
1091 linkedit.vmsize += file_size;
1092 linkedit.filesize += file_size;
1093
1094 log.debug("updated linkedit segment {}\n", .{linkedit});
953 }1095 }
954 }1096 }
955}1097}
9561098
957fn allocateTextBlock(self: *MachO, text_block: *TextBlock, new_block_size: u64, alignment: u64) !u64 {1099fn allocateTextBlock(self: *MachO, text_block: *TextBlock, new_block_size: u64, alignment: u64) !u64 {
958 const segment = &self.load_commands.items[self.segment_cmd_index.?].Segment;
959 const text_section = &self.sections.items[self.text_section_index.?];1100 const text_section = &self.sections.items[self.text_section_index.?];
960 const new_block_ideal_capacity = new_block_size * alloc_num / alloc_den;1101 const new_block_ideal_capacity = new_block_size * alloc_num / alloc_den;
9611102
962 var block_placement: ?*TextBlock = null;1103 var block_placement: ?*TextBlock = null;
963 const addr = blk: {1104 const addr = blk: {
964 if (self.last_text_block) |last| {1105 if (self.last_text_block) |last| {
965 const last_symbol = self.symbol_table.items[last.symbol_table_index.?];1106 const last_symbol = self.local_symbols.items[last.symbol_table_index.?];
966 const ideal_capacity = last.size * alloc_num / alloc_den;1107 // TODO pad out with NOPs and reenable
967 const ideal_capacity_end_addr = last_symbol.n_value + ideal_capacity;1108 // const ideal_capacity = last.size * alloc_num / alloc_den;
968 const new_start_addr = mem.alignForwardGeneric(u64, ideal_capacity_end_addr, alignment);1109 // const ideal_capacity_end_addr = last_symbol.n_value + ideal_capacity;
1110 // const new_start_addr = mem.alignForwardGeneric(u64, ideal_capacity_end_addr, alignment);
1111 const end_addr = last_symbol.n_value + last.size;
1112 const new_start_addr = mem.alignForwardGeneric(u64, end_addr, alignment);
969 block_placement = last;1113 block_placement = last;
970 break :blk new_start_addr;1114 break :blk new_start_addr;
971 } else {1115 } else {
...@@ -982,6 +1126,7 @@ fn allocateTextBlock(self: *MachO, text_block: *TextBlock, new_block_size: u64,...@@ -982,6 +1126,7 @@ fn allocateTextBlock(self: *MachO, text_block: *TextBlock, new_block_size: u64,
982 assert(needed_size <= text_capacity); // TODO handle growth1126 assert(needed_size <= text_capacity); // TODO handle growth
9831127
984 self.last_text_block = text_block;1128 self.last_text_block = text_block;
1129 text_section.size = needed_size; // TODO temp until we pad out with NOPs
985 }1130 }
986 text_block.size = new_block_size;1131 text_block.size = new_block_size;
9871132
...@@ -1019,6 +1164,19 @@ fn makeString(self: *MachO, bytes: []const u8) !u32 {...@@ -1019,6 +1164,19 @@ fn makeString(self: *MachO, bytes: []const u8) !u32 {
1019 return @intCast(u32, result);1164 return @intCast(u32, result);
1020}1165}
10211166
1167fn getString(self: *MachO, str_off: u32) []const u8 {
1168 assert(str_off < self.string_table.items.len);
1169 return mem.spanZ(@ptrCast([*:0]const u8, self.string_table.items.ptr + str_off));
1170}
1171
1172fn updateString(self: *MachO, old_str_off: u32, new_name: []const u8) !u32 {
1173 const existing_name = self.getString(old_str_off);
1174 if (mem.eql(u8, existing_name, new_name)) {
1175 return old_str_off;
1176 }
1177 return self.makeString(new_name);
1178}
1179
1022fn alignSize(comptime Int: type, min_size: anytype, alignment: Int) Int {1180fn alignSize(comptime Int: type, min_size: anytype, alignment: Int) Int {
1023 const size = @intCast(Int, min_size);1181 const size = @intCast(Int, min_size);
1024 if (size % alignment == 0) return size;1182 if (size % alignment == 0) return size;
...@@ -1119,7 +1277,7 @@ fn writeSymbol(self: *MachO, index: usize) !void {...@@ -1119,7 +1277,7 @@ fn writeSymbol(self: *MachO, index: usize) !void {
1119 defer tracy.end();1277 defer tracy.end();
11201278
1121 const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab;1279 const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab;
1122 const sym = [1]macho.nlist_64{self.symbol_table.items[index]};1280 const sym = [1]macho.nlist_64{self.local_symbols.items[index]};
1123 const off = symtab.symoff + @sizeOf(macho.nlist_64) * index;1281 const off = symtab.symoff + @sizeOf(macho.nlist_64) * index;
1124 log.debug("writing symbol {} at 0x{x}\n", .{ sym[0], off });1282 log.debug("writing symbol {} at 0x{x}\n", .{ sym[0], off });
1125 try self.base.file.?.pwriteAll(mem.sliceAsBytes(sym[0..1]), off);1283 try self.base.file.?.pwriteAll(mem.sliceAsBytes(sym[0..1]), off);
...@@ -1135,6 +1293,13 @@ fn writeOffsetTableEntry(self: *MachO, index: usize) !void {...@@ -1135,6 +1293,13 @@ fn writeOffsetTableEntry(self: *MachO, index: usize) !void {
1135 try self.base.file.?.pwriteAll(&buf, off);1293 try self.base.file.?.pwriteAll(&buf, off);
1136}1294}
11371295
1296fn writeAllGlobalSymbols(self: *MachO) !void {
1297 const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab;
1298 const off = symtab.symoff + self.local_symbols.items.len * @sizeOf(macho.nlist_64);
1299 log.debug("writing global symbols from 0x{x} to 0x{x}\n", .{ off, self.global_symbols.items.len + off });
1300 try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.global_symbols.items), off);
1301}
1302
1138/// Writes Mach-O file header.1303/// Writes Mach-O file header.
1139/// Should be invoked last as it needs up-to-date values of ncmds and sizeof_cmds bookkeeping1304/// Should be invoked last as it needs up-to-date values of ncmds and sizeof_cmds bookkeeping
1140/// variables.1305/// variables.