| ... | @@ -6,8 +6,11 @@ const assert = std.debug.assert; | ... | @@ -6,8 +6,11 @@ const assert = std.debug.assert; |
| 6 | const fs = std.fs; | 6 | const fs = std.fs; |
| 7 | const log = std.log.scoped(.link); | 7 | const log = std.log.scoped(.link); |
| 8 | const macho = std.macho; | 8 | const macho = std.macho; |
| | 9 | const codegen = @import("../codegen.zig"); |
| 9 | const math = std.math; | 10 | const math = std.math; |
| 10 | const mem = std.mem; | 11 | const mem = std.mem; |
| | 12 | const trace = @import("../tracy.zig").trace; |
| | 13 | const Type = @import("../type.zig").Type; |
| 11 | | 14 | |
| 12 | const Module = @import("../Module.zig"); | 15 | const Module = @import("../Module.zig"); |
| 13 | const link = @import("../link.zig"); | 16 | const link = @import("../link.zig"); |
| ... | @@ -17,18 +20,35 @@ pub const base_tag: File.Tag = File.Tag.macho; | ... | @@ -17,18 +20,35 @@ pub const base_tag: File.Tag = File.Tag.macho; |
| 17 | | 20 | |
| 18 | base: File, | 21 | base: File, |
| 19 | | 22 | |
| 20 | /// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write. | 23 | /// List of all load command headers that are in the file. |
| 21 | /// Same order as in the file. | 24 | /// We use it to track number and size of all commands needed by the header. |
| 22 | segment_cmds: std.ArrayListUnmanaged(macho.segment_command_64) = std.ArrayListUnmanaged(macho.segment_command_64){}, | 25 | commands: std.ArrayListUnmanaged(macho.load_command) = std.ArrayListUnmanaged(macho.load_command){}, |
| | 26 | command_file_offset: ?u64 = null, |
| 23 | | 27 | |
| 24 | /// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write. | 28 | /// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write. |
| 25 | /// Same order as in the file. | 29 | /// Same order as in the file. |
| | 30 | segments: std.ArrayListUnmanaged(macho.segment_command_64) = std.ArrayListUnmanaged(macho.segment_command_64){}, |
| 26 | sections: std.ArrayListUnmanaged(macho.section_64) = std.ArrayListUnmanaged(macho.section_64){}, | 31 | sections: std.ArrayListUnmanaged(macho.section_64) = std.ArrayListUnmanaged(macho.section_64){}, |
| | 32 | segment_table_offset: ?u64 = null, |
| 27 | | 33 | |
| | 34 | /// Entry point load command |
| | 35 | entry_point_cmd: ?macho.entry_point_command = null, |
| 28 | entry_addr: ?u64 = null, | 36 | entry_addr: ?u64 = null, |
| 29 | | 37 | |
| | 38 | /// Default VM start address set at 4GB |
| | 39 | vm_start_address: u64 = 0x100000000, |
| | 40 | |
| | 41 | seg_table_dirty: bool = false, |
| | 42 | |
| 30 | error_flags: File.ErrorFlags = File.ErrorFlags{}, | 43 | error_flags: File.ErrorFlags = File.ErrorFlags{}, |
| 31 | | 44 | |
| | 45 | /// `alloc_num / alloc_den` is the factor of padding when allocating. |
| | 46 | const alloc_num = 4; |
| | 47 | const alloc_den = 3; |
| | 48 | |
| | 49 | /// Default path to dyld |
| | 50 | const DEFAULT_DYLD_PATH: [*:0]const u8 = "/usr/lib/dyld"; |
| | 51 | |
| 32 | pub const TextBlock = struct { | 52 | pub const TextBlock = struct { |
| 33 | pub const empty = TextBlock{}; | 53 | pub const empty = TextBlock{}; |
| 34 | }; | 54 | }; |
| ... | @@ -80,12 +100,6 @@ fn openFile(allocator: *Allocator, file: fs.File, options: link.Options) !MachO | ... | @@ -80,12 +100,6 @@ fn openFile(allocator: *Allocator, file: fs.File, options: link.Options) !MachO |
| 80 | /// Truncates the existing file contents and overwrites the contents. | 100 | /// Truncates the existing file contents and overwrites the contents. |
| 81 | /// Returns an error if `file` is not already open with +read +write +seek abilities. | 101 | /// Returns an error if `file` is not already open with +read +write +seek abilities. |
| 82 | fn createFile(allocator: *Allocator, file: fs.File, options: link.Options) !MachO { | 102 | fn createFile(allocator: *Allocator, file: fs.File, options: link.Options) !MachO { |
| 83 | switch (options.output_mode) { | | |
| 84 | .Exe => {}, | | |
| 85 | .Obj => {}, | | |
| 86 | .Lib => return error.TODOImplementWritingLibFiles, | | |
| 87 | } | | |
| 88 | | | |
| 89 | var self: MachO = .{ | 103 | var self: MachO = .{ |
| 90 | .base = .{ | 104 | .base = .{ |
| 91 | .file = file, | 105 | .file = file, |
| ... | @@ -96,31 +110,35 @@ fn createFile(allocator: *Allocator, file: fs.File, options: link.Options) !Mach | ... | @@ -96,31 +110,35 @@ fn createFile(allocator: *Allocator, file: fs.File, options: link.Options) !Mach |
| 96 | }; | 110 | }; |
| 97 | errdefer self.deinit(); | 111 | errdefer self.deinit(); |
| 98 | | 112 | |
| 99 | if (options.output_mode == .Exe) { | 113 | switch (options.output_mode) { |
| 100 | // The first segment command for executables is always a __PAGEZERO segment. | 114 | .Exe => { |
| 101 | try self.segment_cmds.append(allocator, .{ | 115 | // The first segment command for executables is always a __PAGEZERO segment. |
| 102 | .cmd = macho.LC_SEGMENT_64, | 116 | const pagezero = .{ |
| 103 | .cmdsize = @sizeOf(macho.segment_command_64), | 117 | .cmd = macho.LC_SEGMENT_64, |
| 104 | .segname = self.makeString("__PAGEZERO"), | 118 | .cmdsize = commandSize(@sizeOf(macho.segment_command_64)), |
| 105 | .vmaddr = 0, | 119 | .segname = makeString("__PAGEZERO"), |
| 106 | .vmsize = 0, | 120 | .vmaddr = 0, |
| 107 | .fileoff = 0, | 121 | .vmsize = self.vm_start_address, |
| 108 | .filesize = 0, | 122 | .fileoff = 0, |
| 109 | .maxprot = 0, | 123 | .filesize = 0, |
| 110 | .initprot = 0, | 124 | .maxprot = 0, |
| 111 | .nsects = 0, | 125 | .initprot = 0, |
| 112 | .flags = 0, | 126 | .nsects = 0, |
| 113 | }); | 127 | .flags = 0, |
| | 128 | }; |
| | 129 | try self.commands.append(allocator, .{ |
| | 130 | .cmd = pagezero.cmd, |
| | 131 | .cmdsize = pagezero.cmdsize, |
| | 132 | }); |
| | 133 | try self.segments.append(allocator, pagezero); |
| | 134 | }, |
| | 135 | .Obj => return error.TODOImplementWritingObjFiles, |
| | 136 | .Lib => return error.TODOImplementWritingLibFiles, |
| 114 | } | 137 | } |
| 115 | | 138 | |
| 116 | return self; | 139 | try self.populateMissingMetadata(); |
| 117 | } | | |
| 118 | | 140 | |
| 119 | fn makeString(self: *MachO, comptime bytes: []const u8) [16]u8 { | 141 | return self; |
| 120 | var buf: [16]u8 = undefined; | | |
| 121 | if (bytes.len > buf.len) @compileError("MachO segment/section name too long"); | | |
| 122 | mem.copy(u8, buf[0..], bytes); | | |
| 123 | return buf; | | |
| 124 | } | 142 | } |
| 125 | | 143 | |
| 126 | fn writeMachOHeader(self: *MachO) !void { | 144 | fn writeMachOHeader(self: *MachO) !void { |
| ... | @@ -156,10 +174,14 @@ fn writeMachOHeader(self: *MachO) !void { | ... | @@ -156,10 +174,14 @@ fn writeMachOHeader(self: *MachO) !void { |
| 156 | }; | 174 | }; |
| 157 | hdr.filetype = filetype; | 175 | hdr.filetype = filetype; |
| 158 | | 176 | |
| 159 | // TODO consider other commands | 177 | const ncmds = try math.cast(u32, self.commands.items.len); |
| 160 | const ncmds = try math.cast(u32, self.segment_cmds.items.len); | | |
| 161 | hdr.ncmds = ncmds; | 178 | hdr.ncmds = ncmds; |
| 162 | hdr.sizeofcmds = ncmds * @sizeOf(macho.segment_command_64); | 179 | |
| | 180 | var sizeof_cmds: u32 = 0; |
| | 181 | for (self.commands.items) |cmd| { |
| | 182 | sizeof_cmds += cmd.cmdsize; |
| | 183 | } |
| | 184 | hdr.sizeofcmds = sizeof_cmds; |
| 163 | | 185 | |
| 164 | // TODO should these be set to something else? | 186 | // TODO should these be set to something else? |
| 165 | hdr.flags = 0; | 187 | hdr.flags = 0; |
| ... | @@ -169,36 +191,117 @@ fn writeMachOHeader(self: *MachO) !void { | ... | @@ -169,36 +191,117 @@ fn writeMachOHeader(self: *MachO) !void { |
| 169 | } | 191 | } |
| 170 | | 192 | |
| 171 | pub fn flush(self: *MachO, module: *Module) !void { | 193 | pub fn flush(self: *MachO, module: *Module) !void { |
| 172 | // TODO implement flush | 194 | // Save segments first |
| 173 | { | 195 | { |
| 174 | const buf = try self.base.allocator.alloc(macho.segment_command_64, self.segment_cmds.items.len); | 196 | const buf = try self.base.allocator.alloc(macho.segment_command_64, self.segments.items.len); |
| 175 | defer self.base.allocator.free(buf); | 197 | defer self.base.allocator.free(buf); |
| 176 | | 198 | |
| | 199 | self.command_file_offset = @sizeOf(macho.mach_header_64); |
| | 200 | |
| 177 | for (buf) |*seg, i| { | 201 | for (buf) |*seg, i| { |
| 178 | seg.* = self.segment_cmds.items[i]; | 202 | seg.* = self.segments.items[i]; |
| | 203 | self.command_file_offset.? += self.segments.items[i].cmdsize; |
| 179 | } | 204 | } |
| 180 | | 205 | |
| 181 | try self.base.file.?.pwriteAll(mem.sliceAsBytes(buf), @sizeOf(macho.mach_header_64)); | 206 | try self.base.file.?.pwriteAll(mem.sliceAsBytes(buf), @sizeOf(macho.mach_header_64)); |
| 182 | } | 207 | } |
| 183 | | 208 | |
| 184 | if (self.entry_addr == null and self.base.options.output_mode == .Exe) { | 209 | switch (self.base.options.output_mode) { |
| 185 | log.debug("flushing. no_entry_point_found = true\n", .{}); | 210 | .Exe => { |
| 186 | self.error_flags.no_entry_point_found = true; | 211 | { |
| 187 | } else { | 212 | // We need to add LC_LOAD_DYLINKER and LC_LOAD_DYLIB since we always |
| 188 | log.debug("flushing. no_entry_point_found = false\n", .{}); | 213 | // have to link against libSystem.dylib |
| 189 | self.error_flags.no_entry_point_found = false; | 214 | const cmdsize = commandSize(@intCast(u32, @sizeOf(macho.dylinker_command) + mem.lenZ(DEFAULT_DYLD_PATH))); |
| 190 | try self.writeMachOHeader(); | 215 | const load_dylinker = [1]macho.dylinker_command{ |
| | 216 | .{ |
| | 217 | .cmd = macho.LC_LOAD_DYLINKER, |
| | 218 | .cmdsize = cmdsize, |
| | 219 | .name = @sizeOf(macho.dylinker_command), |
| | 220 | }, |
| | 221 | }; |
| | 222 | try self.commands.append(self.base.allocator, .{ |
| | 223 | .cmd = macho.LC_LOAD_DYLINKER, |
| | 224 | .cmdsize = cmdsize, |
| | 225 | }); |
| | 226 | |
| | 227 | try self.base.file.?.pwriteAll(mem.sliceAsBytes(load_dylinker[0..1]), self.command_file_offset.?); |
| | 228 | |
| | 229 | const padded_path = try self.base.allocator.alloc(u8, cmdsize - @sizeOf(macho.dylinker_command)); |
| | 230 | defer self.base.allocator.free(padded_path); |
| | 231 | mem.set(u8, padded_path[0..], 0); |
| | 232 | mem.copy(u8, padded_path[0..], mem.spanZ(DEFAULT_DYLD_PATH)); |
| | 233 | |
| | 234 | try self.base.file.?.pwriteAll(padded_path, self.command_file_offset.? + @sizeOf(macho.dylinker_command)); |
| | 235 | self.command_file_offset.? += cmdsize; |
| | 236 | } |
| | 237 | }, |
| | 238 | .Obj => return error.TODOImplementWritingObjFiles, |
| | 239 | .Lib => return error.TODOImplementWritingLibFiles, |
| 191 | } | 240 | } |
| | 241 | |
| | 242 | // if (self.entry_addr == null and self.base.options.output_mode == .Exe) { |
| | 243 | // log.debug("flushing. no_entry_point_found = true\n", .{}); |
| | 244 | // self.error_flags.no_entry_point_found = true; |
| | 245 | // } else { |
| | 246 | log.debug("flushing. no_entry_point_found = false\n", .{}); |
| | 247 | self.error_flags.no_entry_point_found = false; |
| | 248 | try self.writeMachOHeader(); |
| | 249 | // } |
| 192 | } | 250 | } |
| 193 | | 251 | |
| 194 | pub fn deinit(self: *MachO) void { | 252 | pub fn deinit(self: *MachO) void { |
| 195 | self.segment_cmds.deinit(self.base.allocator); | 253 | self.commands.deinit(self.base.allocator); |
| | 254 | self.segments.deinit(self.base.allocator); |
| 196 | self.sections.deinit(self.base.allocator); | 255 | self.sections.deinit(self.base.allocator); |
| 197 | } | 256 | } |
| 198 | | 257 | |
| 199 | pub fn allocateDeclIndexes(self: *MachO, decl: *Module.Decl) !void {} | 258 | pub fn allocateDeclIndexes(self: *MachO, decl: *Module.Decl) !void {} |
| 200 | | 259 | |
| 201 | pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {} | 260 | pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { |
| | 261 | // const tracy = trace(@src()); |
| | 262 | // defer tracy.end(); |
| | 263 | |
| | 264 | // var code_buffer = std.ArrayList(u8).init(self.base.allocator); |
| | 265 | // defer code_buffer.deinit(); |
| | 266 | |
| | 267 | // var dbg_line_buffer = std.ArrayList(u8).init(self.base.allocator); |
| | 268 | // defer dbg_line_buffer.deinit(); |
| | 269 | |
| | 270 | // var dbg_info_buffer = std.ArrayList(u8).init(self.base.allocator); |
| | 271 | // defer dbg_info_buffer.deinit(); |
| | 272 | |
| | 273 | // var dbg_info_type_relocs: File.DbgInfoTypeRelocsTable = .{}; |
| | 274 | // defer { |
| | 275 | // for (dbg_info_type_relocs.items()) |*entry| { |
| | 276 | // entry.value.relocs.deinit(self.base.allocator); |
| | 277 | // } |
| | 278 | // dbg_info_type_relocs.deinit(self.base.allocator); |
| | 279 | // } |
| | 280 | |
| | 281 | // const typed_value = decl.typed_value.most_recent.typed_value; |
| | 282 | // log.debug("typed_value = {}", .{typed_value}); |
| | 283 | |
| | 284 | // const res = try codegen.generateSymbol( |
| | 285 | // &self.base, |
| | 286 | // decl.src(), |
| | 287 | // typed_value, |
| | 288 | // &code_buffer, |
| | 289 | // &dbg_line_buffer, |
| | 290 | // &dbg_info_buffer, |
| | 291 | // &dbg_info_type_relocs, |
| | 292 | // ); |
| | 293 | // log.debug("res = {}", .{res}); |
| | 294 | |
| | 295 | // const code = switch (res) { |
| | 296 | // .externally_managed => |x| x, |
| | 297 | // .appended => code_buffer.items, |
| | 298 | // .fail => |em| { |
| | 299 | // decl.analysis = .codegen_failure; |
| | 300 | // try module.failed_decls.put(module.gpa, decl, em); |
| | 301 | // return; |
| | 302 | // }, |
| | 303 | // }; |
| | 304 | } |
| 202 | | 305 | |
| 203 | pub fn updateDeclLineNumber(self: *MachO, module: *Module, decl: *const Module.Decl) !void {} | 306 | pub fn updateDeclLineNumber(self: *MachO, module: *Module, decl: *const Module.Decl) !void {} |
| 204 | | 307 | |
| ... | @@ -214,3 +317,117 @@ pub fn freeDecl(self: *MachO, decl: *Module.Decl) void {} | ... | @@ -214,3 +317,117 @@ pub fn freeDecl(self: *MachO, decl: *Module.Decl) void {} |
| 214 | pub fn getDeclVAddr(self: *MachO, decl: *const Module.Decl) u64 { | 317 | pub fn getDeclVAddr(self: *MachO, decl: *const Module.Decl) u64 { |
| 215 | @panic("TODO implement getDeclVAddr for MachO"); | 318 | @panic("TODO implement getDeclVAddr for MachO"); |
| 216 | } | 319 | } |
| | 320 | |
| | 321 | pub fn populateMissingMetadata(self: *MachO) !void { |
| | 322 | // if (self.seg_load_re_index == null) { |
| | 323 | // self.seg_load_re_index = @intCast(u16, self.segment_cmds.items.len); |
| | 324 | // const file_size = self.base.options.program_code_size_hint; |
| | 325 | // const p_align = 0x1000; |
| | 326 | // const off = self.findFreeSpace(file_size, p_align); |
| | 327 | // log.debug("found LC_SEGMENT_64 free space 0x{x} to 0x{x}", .{ off, off + file_size }); |
| | 328 | // try self.segment_cmds.append(self.base.allocator, .{}); |
| | 329 | // self.entry_addr = null; |
| | 330 | // self.seg_table_dirty = true; |
| | 331 | // } |
| | 332 | // if (self.seg_got_index == null) { |
| | 333 | // self.seg_got_index = @intCast(u16, self.segment_cmds.items.len); |
| | 334 | // const file_size = 8 * self.base.options.symbol_count_hint; |
| | 335 | // // Apple recommends to page align for better performance. |
| | 336 | // // TODO This is not necessarily true for MH_OBJECT which means we |
| | 337 | // // could potentially shave off a couple of bytes when generating |
| | 338 | // // only object files. |
| | 339 | // const p_align = 0x1000; |
| | 340 | // const off = self.findFreeSpace(file_size, p_align); |
| | 341 | // log.debug("found LC_SEGMENT_64 free space 0x{x} to 0x{x}", .{ off, off + file_size }); |
| | 342 | // const default_vmaddr = 0x4000000; |
| | 343 | // try self.segment_cmds.append(self.base.allocator, .{ |
| | 344 | // .cmd = macho.LC_SEGMENT_64, |
| | 345 | // .cmdsize = @sizeOf(macho.segment_command_64), |
| | 346 | // .segname = self.makeString("__TEXT"), |
| | 347 | // .vmaddr = default_vmaddr, |
| | 348 | // .vmsize = file_size, |
| | 349 | // .fileoff = off, |
| | 350 | // .filesize = file_size, |
| | 351 | // .maxprot = 0x5, |
| | 352 | // .initprot = 0x5, |
| | 353 | // .nsects = 0, |
| | 354 | // .flags = 0, |
| | 355 | // }); |
| | 356 | // self.seg_table_dirty = true; |
| | 357 | // } |
| | 358 | } |
| | 359 | |
| | 360 | /// Returns end pos of collision, if any. |
| | 361 | fn detectAllocCollision(self: *MachO, start: u64, size: u64) ?u64 { |
| | 362 | const header_size: u64 = @sizeOf(macho.mach_header_64); |
| | 363 | if (start < header_size) |
| | 364 | return header_size; |
| | 365 | |
| | 366 | const end = start + satMul(size, alloc_num) / alloc_den; |
| | 367 | |
| | 368 | // if (self.sec_table_offset) |off| { |
| | 369 | // const section_size: u64 = @sizeOf(macho.section_64); |
| | 370 | // const tight_size = self.sections.items.len * section_size; |
| | 371 | // const increased_size = satMul(tight_size, alloc_num) / alloc_den; |
| | 372 | // const test_end = off + increased_size; |
| | 373 | // if (end > off and start < test_end) { |
| | 374 | // return test_end; |
| | 375 | // } |
| | 376 | // } |
| | 377 | |
| | 378 | // if (self.seg_table_offset) |off| { |
| | 379 | // const segment_size: u64 = @sizeOf(macho.segment_command_64); |
| | 380 | // const tight_size = self.segment_cmds.items.len * segment_size; |
| | 381 | // const increased_size = satMul(tight_size, alloc_num) / alloc_den; |
| | 382 | // const test_end = off + increased_size; |
| | 383 | // if (end > off and start < test_end) { |
| | 384 | // return test_end; |
| | 385 | // } |
| | 386 | // } |
| | 387 | |
| | 388 | // for (self.sections.items) |section| { |
| | 389 | // const increased_size = satMul(section.size, alloc_num) / alloc_den; |
| | 390 | // const test_end = section.offset + increased_size; |
| | 391 | // if (end > section.offset and start < test_end) { |
| | 392 | // return test_end; |
| | 393 | // } |
| | 394 | // } |
| | 395 | |
| | 396 | for (self.segments.items) |segment| { |
| | 397 | const increased_size = satMul(segment.filesize, alloc_num) / alloc_den; |
| | 398 | const test_end = segment_cmd.fileoff + increased_size; |
| | 399 | if (end > segment_cmd.fileoff and start < test_end) { |
| | 400 | return test_end; |
| | 401 | } |
| | 402 | } |
| | 403 | |
| | 404 | return null; |
| | 405 | } |
| | 406 | |
| | 407 | fn findFreeSpace(self: *MachO, object_size: u64, min_alignment: u16) u64 { |
| | 408 | var start: u64 = 0; |
| | 409 | while (self.detectAllocCollision(start, object_size)) |item_end| { |
| | 410 | start = mem.alignForwardGeneric(u64, item_end, min_alignment); |
| | 411 | } |
| | 412 | return start; |
| | 413 | } |
| | 414 | |
| | 415 | /// Saturating multiplication |
| | 416 | fn satMul(a: anytype, b: anytype) @TypeOf(a, b) { |
| | 417 | const T = @TypeOf(a, b); |
| | 418 | return std.math.mul(T, a, b) catch std.math.maxInt(T); |
| | 419 | } |
| | 420 | |
| | 421 | fn makeString(comptime bytes: []const u8) [16]u8 { |
| | 422 | var buf: [16]u8 = undefined; |
| | 423 | if (bytes.len > buf.len) @compileError("MachO segment/section name too long"); |
| | 424 | mem.copy(u8, buf[0..], bytes); |
| | 425 | return buf; |
| | 426 | } |
| | 427 | |
| | 428 | fn commandSize(min_size: u32) u32 { |
| | 429 | if (min_size % @sizeOf(u64) == 0) return min_size; |
| | 430 | |
| | 431 | const div = min_size / @sizeOf(u64); |
| | 432 | return (div + 1) * @sizeOf(u64); |
| | 433 | } |