| 1 | pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Path) link.Error!void { |
| 2 | const gpa = comp.gpa; |
| 3 | const io = comp.io; |
| 4 | const diags = &comp.link_diags; |
| 5 | |
| 6 | // TODO: "positional arguments" is a CLI concept, not a linker concept. Delete this unnecessary array list. |
| 7 | var positionals = std.array_list.Managed(link.Input).init(gpa); |
| 8 | defer positionals.deinit(); |
| 9 | try positionals.ensureUnusedCapacity(comp.link_inputs.len); |
| 10 | positionals.appendSliceAssumeCapacity(comp.link_inputs); |
| 11 | |
| 12 | for (comp.c_objects.items) |c_object| { |
| 13 | try positionals.append(try link.openObjectInput(io, diags, c_object.status.success.object_path)); |
| 14 | } |
| 15 | |
| 16 | if (module_obj_path) |path| try positionals.append(try link.openObjectInput(io, diags, path)); |
| 17 | |
| 18 | if (macho_file.getZigObject() == null and positionals.items.len == 1) { |
| 19 | // Instead of invoking a full-blown `-r` mode on the input which sadly will strip all |
| 20 | // debug info segments/sections (this is apparently by design by Apple), we copy |
| 21 | // the *only* input file over. |
| 22 | const path = positionals.items[0].path().?; |
| 23 | const in_file = path.root_dir.handle.openFile(io, path.sub_path, .{}) catch |err| |
| 24 | return diags.fail("failed to open {f}: {s}", .{ path, @errorName(err) }); |
| 25 | const stat = in_file.stat(io) catch |err| |
| 26 | return diags.fail("failed to stat {f}: {s}", .{ path, @errorName(err) }); |
| 27 | link.File.copyRangeAll2(io, in_file, macho_file.base.file.?, 0, 0, stat.size) catch |err| |
| 28 | return diags.fail("failed to copy range of file {f}: {t}", .{ path, err }); |
| 29 | return; |
| 30 | } |
| 31 | |
| 32 | for (positionals.items) |link_input| { |
| 33 | macho_file.classifyInputFile(link_input) catch |err| |
| 34 | diags.addParseError(link_input.path().?, "failed to read input file: {s}", .{@errorName(err)}); |
| 35 | } |
| 36 | |
| 37 | if (diags.hasErrors()) return error.AlreadyReported; |
| 38 | |
| 39 | try macho_file.parseInputFiles(); |
| 40 | |
| 41 | if (diags.hasErrors()) return error.AlreadyReported; |
| 42 | |
| 43 | try macho_file.resolveSymbols(); |
| 44 | macho_file.dedupLiterals() catch |err| switch (err) { |
| 45 | error.OutOfMemory, error.AlreadyReported => |e| return e, |
| 46 | else => |e| return diags.fail("failed to update ar size: {s}", .{@errorName(e)}), |
| 47 | }; |
| 48 | markExports(macho_file); |
| 49 | claimUnresolved(macho_file); |
| 50 | try initOutputSections(macho_file); |
| 51 | try macho_file.sortSections(); |
| 52 | try macho_file.addAtomsToSections(); |
| 53 | try calcSectionSizes(macho_file); |
| 54 | |
| 55 | try createSegment(macho_file); |
| 56 | allocateSections(macho_file) catch |err| switch (err) { |
| 57 | error.AlreadyReported => |e| return e, |
| 58 | else => |e| return diags.fail("failed to allocate sections: {s}", .{@errorName(e)}), |
| 59 | }; |
| 60 | allocateSegment(macho_file); |
| 61 | |
| 62 | if (build_options.enable_logging) { |
| 63 | state_log.debug("{f}", .{macho_file.dumpState()}); |
| 64 | } |
| 65 | |
| 66 | try writeSections(macho_file); |
| 67 | sortRelocs(macho_file); |
| 68 | try writeSectionsToFile(macho_file); |
| 69 | |
| 70 | // In order to please Apple ld (and possibly other MachO linkers in the wild), |
| 71 | // we will now sanitize segment names of Zig-specific segments. |
| 72 | sanitizeZigSections(macho_file); |
| 73 | |
| 74 | const ncmds, const sizeofcmds = try writeLoadCommands(macho_file); |
| 75 | try writeHeader(macho_file, ncmds, sizeofcmds); |
| 76 | } |
| 77 | |
| 78 | pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Path) link.Error!void { |
| 79 | const gpa = comp.gpa; |
| 80 | const io = comp.io; |
| 81 | const diags = &macho_file.base.comp.link_diags; |
| 82 | |
| 83 | var positionals = std.array_list.Managed(link.Input).init(gpa); |
| 84 | defer positionals.deinit(); |
| 85 | |
| 86 | try positionals.ensureUnusedCapacity(comp.link_inputs.len); |
| 87 | positionals.appendSliceAssumeCapacity(comp.link_inputs); |
| 88 | |
| 89 | for (comp.c_objects.items) |c_object| { |
| 90 | try positionals.append(try link.openObjectInput(io, diags, c_object.status.success.object_path)); |
| 91 | } |
| 92 | |
| 93 | if (module_obj_path) |path| try positionals.append(try link.openObjectInput(io, diags, path)); |
| 94 | |
| 95 | if (comp.compiler_rt_strat == .obj) { |
| 96 | try positionals.append(try link.openObjectInput(io, diags, comp.compiler_rt_obj.?.full_object_path)); |
| 97 | } |
| 98 | |
| 99 | if (comp.ubsan_rt_strat == .obj) { |
| 100 | try positionals.append(try link.openObjectInput(io, diags, comp.ubsan_rt_obj.?.full_object_path)); |
| 101 | } |
| 102 | |
| 103 | for (positionals.items) |link_input| { |
| 104 | macho_file.classifyInputFile(link_input) catch |err| |
| 105 | diags.addParseError(link_input.path().?, "failed to read input file: {s}", .{@errorName(err)}); |
| 106 | } |
| 107 | |
| 108 | if (diags.hasErrors()) return error.AlreadyReported; |
| 109 | |
| 110 | try parseInputFilesAr(macho_file); |
| 111 | |
| 112 | if (diags.hasErrors()) return error.AlreadyReported; |
| 113 | |
| 114 | // First, we flush relocatable object file generated with our backends. |
| 115 | if (macho_file.getZigObject()) |zo| { |
| 116 | try zo.resolveSymbols(macho_file); |
| 117 | zo.asFile().markExportsRelocatable(macho_file); |
| 118 | zo.asFile().claimUnresolvedRelocatable(macho_file); |
| 119 | try macho_file.sortSections(); |
| 120 | try macho_file.addAtomsToSections(); |
| 121 | try calcSectionSizes(macho_file); |
| 122 | try createSegment(macho_file); |
| 123 | allocateSections(macho_file) catch |err| |
| 124 | return diags.fail("failed to allocate sections: {s}", .{@errorName(err)}); |
| 125 | allocateSegment(macho_file); |
| 126 | |
| 127 | if (build_options.enable_logging) { |
| 128 | state_log.debug("{f}", .{macho_file.dumpState()}); |
| 129 | } |
| 130 | |
| 131 | try writeSections(macho_file); |
| 132 | sortRelocs(macho_file); |
| 133 | try writeSectionsToFile(macho_file); |
| 134 | |
| 135 | // In order to please Apple ld (and possibly other MachO linkers in the wild), |
| 136 | // we will now sanitize segment names of Zig-specific segments. |
| 137 | sanitizeZigSections(macho_file); |
| 138 | |
| 139 | const ncmds, const sizeofcmds = try writeLoadCommands(macho_file); |
| 140 | try writeHeader(macho_file, ncmds, sizeofcmds); |
| 141 | |
| 142 | try zo.readFileContents(macho_file); |
| 143 | } |
| 144 | |
| 145 | var files = std.array_list.Managed(File.Index).init(gpa); |
| 146 | defer files.deinit(); |
| 147 | try files.ensureTotalCapacityPrecise(macho_file.objects.items.len + 1); |
| 148 | if (macho_file.getZigObject()) |zo| files.appendAssumeCapacity(zo.index); |
| 149 | for (macho_file.objects.items) |index| files.appendAssumeCapacity(index); |
| 150 | |
| 151 | const format: Archive.Format = .p32; |
| 152 | |
| 153 | // Update ar symtab from parsed objects |
| 154 | var ar_symtab: Archive.ArSymtab = .{}; |
| 155 | defer ar_symtab.deinit(gpa); |
| 156 | |
| 157 | for (files.items) |index| { |
| 158 | try macho_file.getFile(index).?.updateArSymtab(&ar_symtab, macho_file); |
| 159 | } |
| 160 | |
| 161 | ar_symtab.sort(); |
| 162 | |
| 163 | // Update sizes of contributing objects |
| 164 | for (files.items) |index| { |
| 165 | macho_file.getFile(index).?.updateArSize(macho_file) catch |err| |
| 166 | return diags.fail("failed to update ar size: {s}", .{@errorName(err)}); |
| 167 | } |
| 168 | |
| 169 | // Update file offsets of contributing objects |
| 170 | const total_size: usize = blk: { |
| 171 | var pos: usize = Archive.SARMAG; |
| 172 | pos += @sizeOf(Archive.ar_hdr); |
| 173 | pos += Archive.SYMDEF.len + 1; |
| 174 | pos = mem.alignForward(usize, pos, 8); |
| 175 | pos += ar_symtab.size(format); |
| 176 | |
| 177 | for (files.items) |index| { |
| 178 | const file = macho_file.getFile(index).?; |
| 179 | switch (file) { |
| 180 | .zig_object => |zo| { |
| 181 | const state = &zo.output_ar_state; |
| 182 | pos = mem.alignForward(usize, pos, 2); |
| 183 | state.file_off = pos; |
| 184 | pos += @sizeOf(Archive.ar_hdr); |
| 185 | pos += zo.basename.len + 1; |
| 186 | pos = mem.alignForward(usize, pos, 8); |
| 187 | pos += try macho_file.cast(usize, state.size); |
| 188 | }, |
| 189 | .object => |o| { |
| 190 | const state = &o.output_ar_state; |
| 191 | pos = mem.alignForward(usize, pos, 2); |
| 192 | state.file_off = pos; |
| 193 | pos += @sizeOf(Archive.ar_hdr); |
| 194 | pos += std.fs.path.basename(o.path).len + 1; |
| 195 | pos = mem.alignForward(usize, pos, 8); |
| 196 | pos += try macho_file.cast(usize, state.size); |
| 197 | }, |
| 198 | else => unreachable, |
| 199 | } |
| 200 | } |
| 201 | |
| 202 | break :blk pos; |
| 203 | }; |
| 204 | |
| 205 | if (build_options.enable_logging) { |
| 206 | state_log.debug("ar_symtab\n{f}\n", .{ar_symtab.fmt(macho_file)}); |
| 207 | } |
| 208 | |
| 209 | const buffer = try gpa.alloc(u8, total_size); |
| 210 | defer gpa.free(buffer); |
| 211 | var writer: Writer = .fixed(buffer); |
| 212 | |
| 213 | // Write magic |
| 214 | writer.writeAll(Archive.ARMAG) catch unreachable; |
| 215 | |
| 216 | // Write symtab |
| 217 | ar_symtab.write(format, macho_file, &writer) catch |err| |
| 218 | return diags.fail("failed to write archive symbol table: {t}", .{err}); |
| 219 | |
| 220 | // Write object files |
| 221 | for (files.items) |index| { |
| 222 | const aligned = mem.alignForward(usize, writer.end, 2); |
| 223 | const padding = aligned - writer.end; |
| 224 | if (padding > 0) { |
| 225 | writer.splatByteAll(0, padding) catch unreachable; |
| 226 | } |
| 227 | macho_file.getFile(index).?.writeAr(macho_file, &writer) catch |err| |
| 228 | return diags.fail("failed to write archive: {t}", .{err}); |
| 229 | } |
| 230 | |
| 231 | assert(writer.end == total_size); |
| 232 | |
| 233 | try macho_file.setLength(total_size); |
| 234 | try macho_file.pwriteAll(writer.buffered(), 0); |
| 235 | |
| 236 | if (diags.hasErrors()) return error.AlreadyReported; |
| 237 | } |
| 238 | |
| 239 | fn parseInputFilesAr(macho_file: *MachO) !void { |
| 240 | const tracy = trace(@src()); |
| 241 | defer tracy.end(); |
| 242 | |
| 243 | for (macho_file.objects.items) |index| { |
| 244 | macho_file.getFile(index).?.parseAr(macho_file) catch |err| switch (err) { |
| 245 | error.InvalidMachineType => {}, // already reported |
| 246 | else => |e| try macho_file.reportParseError2(index, "unexpected error: parsing input file failed with error {s}", .{@errorName(e)}), |
| 247 | }; |
| 248 | } |
| 249 | } |
| 250 | |
| 251 | fn markExports(macho_file: *MachO) void { |
| 252 | if (macho_file.getZigObject()) |zo| { |
| 253 | zo.asFile().markExportsRelocatable(macho_file); |
| 254 | } |
| 255 | for (macho_file.objects.items) |index| { |
| 256 | macho_file.getFile(index).?.markExportsRelocatable(macho_file); |
| 257 | } |
| 258 | } |
| 259 | |
| 260 | pub fn claimUnresolved(macho_file: *MachO) void { |
| 261 | if (macho_file.getZigObject()) |zo| { |
| 262 | zo.asFile().claimUnresolvedRelocatable(macho_file); |
| 263 | } |
| 264 | for (macho_file.objects.items) |index| { |
| 265 | macho_file.getFile(index).?.claimUnresolvedRelocatable(macho_file); |
| 266 | } |
| 267 | } |
| 268 | |
| 269 | fn initOutputSections(macho_file: *MachO) !void { |
| 270 | for (macho_file.objects.items) |index| { |
| 271 | const file = macho_file.getFile(index).?; |
| 272 | for (file.getAtoms()) |atom_index| { |
| 273 | const atom = file.getAtom(atom_index) orelse continue; |
| 274 | if (!atom.isAlive()) continue; |
| 275 | atom.out_n_sect = try Atom.initOutputSection(atom.getInputSection(macho_file), macho_file); |
| 276 | } |
| 277 | } |
| 278 | |
| 279 | const needs_unwind_info = for (macho_file.objects.items) |index| { |
| 280 | if (macho_file.getFile(index).?.object.hasUnwindRecords()) break true; |
| 281 | } else false; |
| 282 | if (needs_unwind_info) { |
| 283 | macho_file.unwind_info_sect_index = try macho_file.addSection("__LD", "__compact_unwind", .{ |
| 284 | .flags = macho.S_ATTR_DEBUG, |
| 285 | }); |
| 286 | } |
| 287 | |
| 288 | const needs_eh_frame = for (macho_file.objects.items) |index| { |
| 289 | if (macho_file.getFile(index).?.object.hasEhFrameRecords()) break true; |
| 290 | } else false; |
| 291 | if (needs_eh_frame) { |
| 292 | assert(needs_unwind_info); |
| 293 | macho_file.eh_frame_sect_index = try macho_file.addSection("__TEXT", "__eh_frame", .{ |
| 294 | .flags = std.macho.S_COALESCED | std.macho.S_ATTR_NO_TOC | std.macho.S_ATTR_STRIP_STATIC_SYMS | std.macho.S_ATTR_LIVE_SUPPORT, |
| 295 | }); |
| 296 | } |
| 297 | } |
| 298 | |
| 299 | fn calcSectionSizes(macho_file: *MachO) !void { |
| 300 | const tracy = trace(@src()); |
| 301 | defer tracy.end(); |
| 302 | |
| 303 | const diags = &macho_file.base.comp.link_diags; |
| 304 | |
| 305 | if (macho_file.getZigObject()) |zo| { |
| 306 | // TODO this will create a race as we need to track merging of debug sections which we currently don't |
| 307 | zo.calcNumRelocs(macho_file); |
| 308 | } |
| 309 | |
| 310 | { |
| 311 | for (macho_file.sections.items(.atoms), 0..) |atoms, i| { |
| 312 | if (atoms.items.len == 0) continue; |
| 313 | calcSectionSizeWorker(macho_file, @as(u8, @intCast(i))); |
| 314 | } |
| 315 | |
| 316 | if (macho_file.eh_frame_sect_index) |_| { |
| 317 | calcEhFrameSizeWorker(macho_file); |
| 318 | } |
| 319 | |
| 320 | if (macho_file.unwind_info_sect_index) |_| { |
| 321 | for (macho_file.objects.items) |index| { |
| 322 | Object.calcCompactUnwindSizeRelocatable( |
| 323 | macho_file.getFile(index).?.object, |
| 324 | macho_file, |
| 325 | ); |
| 326 | } |
| 327 | } |
| 328 | |
| 329 | for (macho_file.objects.items) |index| { |
| 330 | File.calcSymtabSize(macho_file.getFile(index).?, macho_file); |
| 331 | } |
| 332 | if (macho_file.getZigObject()) |zo| { |
| 333 | File.calcSymtabSize(zo.asFile(), macho_file); |
| 334 | } |
| 335 | |
| 336 | MachO.updateLinkeditSizeWorker(macho_file, .data_in_code); |
| 337 | } |
| 338 | |
| 339 | if (macho_file.unwind_info_sect_index) |_| { |
| 340 | calcCompactUnwindSize(macho_file); |
| 341 | } |
| 342 | try calcSymtabSize(macho_file); |
| 343 | |
| 344 | if (diags.hasErrors()) return error.AlreadyReported; |
| 345 | } |
| 346 | |
| 347 | fn calcSectionSizeWorker(macho_file: *MachO, sect_id: u8) void { |
| 348 | const tracy = trace(@src()); |
| 349 | defer tracy.end(); |
| 350 | |
| 351 | const slice = macho_file.sections.slice(); |
| 352 | const header = &slice.items(.header)[sect_id]; |
| 353 | const atoms = slice.items(.atoms)[sect_id].items; |
| 354 | for (atoms) |ref| { |
| 355 | const atom = ref.getAtom(macho_file).?; |
| 356 | const atom_alignment = atom.alignment.toByteUnits() orelse 1; |
| 357 | const offset = mem.alignForward(u64, header.size, atom_alignment); |
| 358 | const padding = offset - header.size; |
| 359 | atom.value = offset; |
| 360 | header.size += padding + atom.size; |
| 361 | header.@"align" = @max(header.@"align", atom.alignment.toLog2Units()); |
| 362 | const nreloc = atom.calcNumRelocs(macho_file); |
| 363 | atom.addExtra(.{ .rel_out_index = header.nreloc, .rel_out_count = nreloc }, macho_file); |
| 364 | header.nreloc += nreloc; |
| 365 | } |
| 366 | } |
| 367 | |
| 368 | fn calcEhFrameSizeWorker(macho_file: *MachO) void { |
| 369 | const tracy = trace(@src()); |
| 370 | defer tracy.end(); |
| 371 | |
| 372 | const diags = &macho_file.base.comp.link_diags; |
| 373 | |
| 374 | const doWork = struct { |
| 375 | fn doWork(mfile: *MachO, header: *macho.section_64) !void { |
| 376 | header.size = try eh_frame.calcSize(mfile); |
| 377 | header.@"align" = 3; |
| 378 | header.nreloc = eh_frame.calcNumRelocs(mfile); |
| 379 | } |
| 380 | }.doWork; |
| 381 | |
| 382 | const header = &macho_file.sections.items(.header)[macho_file.eh_frame_sect_index.?]; |
| 383 | doWork(macho_file, header) catch |err| |
| 384 | diags.addError("failed to calculate size of section '__TEXT,__eh_frame': {s}", .{@errorName(err)}); |
| 385 | } |
| 386 | |
| 387 | fn calcCompactUnwindSize(macho_file: *MachO) void { |
| 388 | const tracy = trace(@src()); |
| 389 | defer tracy.end(); |
| 390 | |
| 391 | var nrec: u32 = 0; |
| 392 | var nreloc: u32 = 0; |
| 393 | |
| 394 | for (macho_file.objects.items) |index| { |
| 395 | const ctx = &macho_file.getFile(index).?.object.compact_unwind_ctx; |
| 396 | ctx.rec_index = nrec; |
| 397 | ctx.reloc_index = nreloc; |
| 398 | nrec += ctx.rec_count; |
| 399 | nreloc += ctx.reloc_count; |
| 400 | } |
| 401 | |
| 402 | const sect = &macho_file.sections.items(.header)[macho_file.unwind_info_sect_index.?]; |
| 403 | sect.size = nrec * @sizeOf(macho.compact_unwind_entry); |
| 404 | sect.nreloc = nreloc; |
| 405 | sect.@"align" = 3; |
| 406 | } |
| 407 | |
| 408 | fn calcSymtabSize(macho_file: *MachO) error{OutOfMemory}!void { |
| 409 | const tracy = trace(@src()); |
| 410 | defer tracy.end(); |
| 411 | |
| 412 | const gpa = macho_file.base.comp.gpa; |
| 413 | |
| 414 | var nlocals: u32 = 0; |
| 415 | var nstabs: u32 = 0; |
| 416 | var nexports: u32 = 0; |
| 417 | var nimports: u32 = 0; |
| 418 | var strsize: u32 = 1; |
| 419 | |
| 420 | var objects = try std.array_list.Managed(File.Index).initCapacity(gpa, macho_file.objects.items.len + 1); |
| 421 | defer objects.deinit(); |
| 422 | if (macho_file.getZigObject()) |zo| objects.appendAssumeCapacity(zo.index); |
| 423 | objects.appendSliceAssumeCapacity(macho_file.objects.items); |
| 424 | |
| 425 | for (objects.items) |index| { |
| 426 | const ctx = switch (macho_file.getFile(index).?) { |
| 427 | inline else => |x| &x.output_symtab_ctx, |
| 428 | }; |
| 429 | ctx.ilocal = nlocals; |
| 430 | ctx.istab = nstabs; |
| 431 | ctx.iexport = nexports; |
| 432 | ctx.iimport = nimports; |
| 433 | ctx.stroff = strsize; |
| 434 | nlocals += ctx.nlocals; |
| 435 | nstabs += ctx.nstabs; |
| 436 | nexports += ctx.nexports; |
| 437 | nimports += ctx.nimports; |
| 438 | strsize += ctx.strsize; |
| 439 | } |
| 440 | |
| 441 | for (objects.items) |index| { |
| 442 | const ctx = switch (macho_file.getFile(index).?) { |
| 443 | inline else => |x| &x.output_symtab_ctx, |
| 444 | }; |
| 445 | ctx.istab += nlocals; |
| 446 | ctx.iexport += nlocals + nstabs; |
| 447 | ctx.iimport += nlocals + nstabs + nexports; |
| 448 | } |
| 449 | |
| 450 | { |
| 451 | const cmd = &macho_file.symtab_cmd; |
| 452 | cmd.nsyms = nlocals + nstabs + nexports + nimports; |
| 453 | cmd.strsize = strsize; |
| 454 | } |
| 455 | |
| 456 | { |
| 457 | const cmd = &macho_file.dysymtab_cmd; |
| 458 | cmd.ilocalsym = 0; |
| 459 | cmd.nlocalsym = nlocals + nstabs; |
| 460 | cmd.iextdefsym = nlocals + nstabs; |
| 461 | cmd.nextdefsym = nexports; |
| 462 | cmd.iundefsym = nlocals + nstabs + nexports; |
| 463 | cmd.nundefsym = nimports; |
| 464 | } |
| 465 | } |
| 466 | |
| 467 | fn allocateSections(macho_file: *MachO) !void { |
| 468 | const slice = macho_file.sections.slice(); |
| 469 | for (slice.items(.header)) |*header| { |
| 470 | const needed_size = header.size; |
| 471 | header.size = 0; |
| 472 | const alignment = try macho_file.alignPow(header.@"align"); |
| 473 | if (!header.isZerofill()) { |
| 474 | if (needed_size > macho_file.allocatedSize(header.offset)) { |
| 475 | header.offset = try macho_file.cast(u32, try macho_file.findFreeSpace(needed_size, alignment)); |
| 476 | } |
| 477 | } |
| 478 | if (needed_size > macho_file.allocatedSizeVirtual(header.addr)) { |
| 479 | header.addr = macho_file.findFreeSpaceVirtual(needed_size, alignment); |
| 480 | } |
| 481 | header.size = needed_size; |
| 482 | } |
| 483 | |
| 484 | var fileoff: u32 = 0; |
| 485 | for (slice.items(.header)) |header| { |
| 486 | fileoff = @max(fileoff, header.offset + @as(u32, @intCast(header.size))); |
| 487 | } |
| 488 | |
| 489 | for (slice.items(.header)) |*header| { |
| 490 | if (header.nreloc == 0) continue; |
| 491 | header.reloff = mem.alignForward(u32, fileoff, @alignOf(macho.relocation_info)); |
| 492 | fileoff = header.reloff + header.nreloc * @sizeOf(macho.relocation_info); |
| 493 | } |
| 494 | |
| 495 | // In -r mode, there is no LINKEDIT segment and so we allocate required LINKEDIT commands |
| 496 | // as if they were detached or part of the single segment. |
| 497 | |
| 498 | // DATA_IN_CODE |
| 499 | { |
| 500 | const cmd = &macho_file.data_in_code_cmd; |
| 501 | cmd.dataoff = fileoff; |
| 502 | fileoff += cmd.datasize; |
| 503 | fileoff = mem.alignForward(u32, fileoff, @alignOf(u64)); |
| 504 | } |
| 505 | |
| 506 | // SYMTAB |
| 507 | { |
| 508 | const cmd = &macho_file.symtab_cmd; |
| 509 | cmd.symoff = fileoff; |
| 510 | fileoff += cmd.nsyms * @sizeOf(macho.nlist_64); |
| 511 | fileoff = mem.alignForward(u32, fileoff, @alignOf(u32)); |
| 512 | cmd.stroff = fileoff; |
| 513 | } |
| 514 | } |
| 515 | |
| 516 | /// Renames segment names in Zig sections to standard MachO segment names such as |
| 517 | /// `__TEXT`, `__DATA_CONST` and `__DATA`. |
| 518 | /// TODO: I think I may be able to get rid of this if I rework section/segment |
| 519 | /// allocation mechanism to not rely so much on having `_ZIG` sections always |
| 520 | /// pushed to the back. For instance, this is not a problem in ELF linker. |
| 521 | /// Then, we can create sections with the correct name from the start in `MachO.initMetadata`. |
| 522 | fn sanitizeZigSections(macho_file: *MachO) void { |
| 523 | if (macho_file.zig_text_sect_index) |index| { |
| 524 | const header = &macho_file.sections.items(.header)[index]; |
| 525 | header.segname = MachO.makeStaticString("__TEXT"); |
| 526 | } |
| 527 | if (macho_file.zig_const_sect_index) |index| { |
| 528 | const header = &macho_file.sections.items(.header)[index]; |
| 529 | header.segname = MachO.makeStaticString("__DATA_CONST"); |
| 530 | } |
| 531 | if (macho_file.zig_data_sect_index) |index| { |
| 532 | const header = &macho_file.sections.items(.header)[index]; |
| 533 | header.segname = MachO.makeStaticString("__DATA"); |
| 534 | } |
| 535 | if (macho_file.zig_bss_sect_index) |index| { |
| 536 | const header = &macho_file.sections.items(.header)[index]; |
| 537 | header.segname = MachO.makeStaticString("__DATA"); |
| 538 | } |
| 539 | } |
| 540 | |
| 541 | fn createSegment(macho_file: *MachO) !void { |
| 542 | const gpa = macho_file.base.comp.gpa; |
| 543 | |
| 544 | // For relocatable, we only ever need a single segment so create it now. |
| 545 | const prot: macho.vm_prot_t = .{ .READ = true, .WRITE = true, .EXEC = true }; |
| 546 | try macho_file.segments.append(gpa, .{ |
| 547 | .cmdsize = @sizeOf(macho.segment_command_64), |
| 548 | .segname = MachO.makeStaticString(""), |
| 549 | .maxprot = prot, |
| 550 | .initprot = prot, |
| 551 | }); |
| 552 | const seg = &macho_file.segments.items[0]; |
| 553 | seg.nsects = @intCast(macho_file.sections.items(.header).len); |
| 554 | seg.cmdsize += seg.nsects * @sizeOf(macho.section_64); |
| 555 | } |
| 556 | |
| 557 | fn allocateSegment(macho_file: *MachO) void { |
| 558 | // Allocate the single segment. |
| 559 | const seg = &macho_file.segments.items[0]; |
| 560 | var vmaddr: u64 = 0; |
| 561 | var fileoff: u64 = load_commands.calcLoadCommandsSizeObject(macho_file) + @sizeOf(macho.mach_header_64); |
| 562 | seg.vmaddr = vmaddr; |
| 563 | seg.fileoff = fileoff; |
| 564 | |
| 565 | for (macho_file.sections.items(.header)) |header| { |
| 566 | vmaddr = @max(vmaddr, header.addr + header.size); |
| 567 | if (!header.isZerofill()) { |
| 568 | fileoff = @max(fileoff, header.offset + header.size); |
| 569 | } |
| 570 | } |
| 571 | |
| 572 | seg.vmsize = vmaddr - seg.vmaddr; |
| 573 | seg.filesize = fileoff - seg.fileoff; |
| 574 | } |
| 575 | |
| 576 | // We need to sort relocations in descending order to be compatible with Apple's linker. |
| 577 | fn sortReloc(ctx: void, lhs: macho.relocation_info, rhs: macho.relocation_info) bool { |
| 578 | _ = ctx; |
| 579 | return lhs.r_address > rhs.r_address; |
| 580 | } |
| 581 | |
| 582 | fn sortRelocs(macho_file: *MachO) void { |
| 583 | const tracy = trace(@src()); |
| 584 | defer tracy.end(); |
| 585 | |
| 586 | for (macho_file.sections.items(.relocs)) |*relocs| { |
| 587 | mem.sort(macho.relocation_info, relocs.items, {}, sortReloc); |
| 588 | } |
| 589 | } |
| 590 | |
| 591 | fn writeSections(macho_file: *MachO) link.Error!void { |
| 592 | const tracy = trace(@src()); |
| 593 | defer tracy.end(); |
| 594 | |
| 595 | const gpa = macho_file.base.comp.gpa; |
| 596 | const diags = &macho_file.base.comp.link_diags; |
| 597 | const cpu_arch = macho_file.getTarget().cpu.arch; |
| 598 | const slice = macho_file.sections.slice(); |
| 599 | for (slice.items(.header), slice.items(.out), slice.items(.relocs), 0..) |header, *out, *relocs, n_sect| { |
| 600 | if (header.isZerofill()) continue; |
| 601 | if (!macho_file.isZigSection(@intCast(n_sect))) { // TODO this is wrong; what about debug sections? |
| 602 | const size = try macho_file.cast(usize, header.size); |
| 603 | try out.resize(gpa, size); |
| 604 | const padding_byte: u8 = if (header.isCode() and cpu_arch == .x86_64) 0xcc else 0; |
| 605 | @memset(out.items, padding_byte); |
| 606 | } |
| 607 | try relocs.resize(gpa, header.nreloc); |
| 608 | } |
| 609 | |
| 610 | const cmd = macho_file.symtab_cmd; |
| 611 | try macho_file.symtab.resize(gpa, cmd.nsyms); |
| 612 | try macho_file.strtab.resize(gpa, cmd.strsize); |
| 613 | macho_file.strtab.items[0] = 0; |
| 614 | |
| 615 | { |
| 616 | for (macho_file.objects.items) |index| { |
| 617 | writeAtomsWorker(macho_file, macho_file.getFile(index).?); |
| 618 | File.writeSymtab(macho_file.getFile(index).?, macho_file, macho_file); |
| 619 | } |
| 620 | |
| 621 | if (macho_file.getZigObject()) |zo| { |
| 622 | writeAtomsWorker(macho_file, zo.asFile()); |
| 623 | File.writeSymtab(zo.asFile(), macho_file, macho_file); |
| 624 | } |
| 625 | |
| 626 | if (macho_file.eh_frame_sect_index) |_| { |
| 627 | writeEhFrameWorker(macho_file); |
| 628 | } |
| 629 | |
| 630 | if (macho_file.unwind_info_sect_index) |_| { |
| 631 | for (macho_file.objects.items) |index| { |
| 632 | writeCompactUnwindWorker(macho_file, macho_file.getFile(index).?.object); |
| 633 | } |
| 634 | } |
| 635 | } |
| 636 | |
| 637 | if (diags.hasErrors()) return error.AlreadyReported; |
| 638 | |
| 639 | if (macho_file.getZigObject()) |zo| { |
| 640 | try zo.writeRelocs(macho_file); |
| 641 | } |
| 642 | } |
| 643 | |
| 644 | fn writeAtomsWorker(macho_file: *MachO, file: File) void { |
| 645 | const tracy = trace(@src()); |
| 646 | defer tracy.end(); |
| 647 | file.writeAtomsRelocatable(macho_file) catch |err| { |
| 648 | macho_file.reportParseError2(file.getIndex(), "failed to write atoms: {s}", .{ |
| 649 | @errorName(err), |
| 650 | }) catch {}; |
| 651 | }; |
| 652 | } |
| 653 | |
| 654 | fn writeEhFrameWorker(macho_file: *MachO) void { |
| 655 | const tracy = trace(@src()); |
| 656 | defer tracy.end(); |
| 657 | |
| 658 | const diags = &macho_file.base.comp.link_diags; |
| 659 | const sect_index = macho_file.eh_frame_sect_index.?; |
| 660 | const buffer = macho_file.sections.items(.out)[sect_index]; |
| 661 | const relocs = macho_file.sections.items(.relocs)[sect_index]; |
| 662 | eh_frame.writeRelocs(macho_file, buffer.items, relocs.items) catch |err| |
| 663 | diags.addError("failed to write '__LD,__eh_frame' section: {s}", .{@errorName(err)}); |
| 664 | } |
| 665 | |
| 666 | fn writeCompactUnwindWorker(macho_file: *MachO, object: *Object) void { |
| 667 | const tracy = trace(@src()); |
| 668 | defer tracy.end(); |
| 669 | |
| 670 | const diags = &macho_file.base.comp.link_diags; |
| 671 | object.writeCompactUnwindRelocatable(macho_file) catch |err| |
| 672 | diags.addError("failed to write '__LD,__eh_frame' section: {s}", .{@errorName(err)}); |
| 673 | } |
| 674 | |
| 675 | fn writeSectionsToFile(macho_file: *MachO) !void { |
| 676 | const tracy = trace(@src()); |
| 677 | defer tracy.end(); |
| 678 | |
| 679 | const slice = macho_file.sections.slice(); |
| 680 | for (slice.items(.header), slice.items(.out), slice.items(.relocs)) |header, out, relocs| { |
| 681 | try macho_file.pwriteAll(out.items, header.offset); |
| 682 | try macho_file.pwriteAll(@ptrCast(relocs.items), header.reloff); |
| 683 | } |
| 684 | |
| 685 | try macho_file.writeDataInCode(); |
| 686 | try macho_file.pwriteAll(@ptrCast(macho_file.symtab.items), macho_file.symtab_cmd.symoff); |
| 687 | try macho_file.pwriteAll(macho_file.strtab.items, macho_file.symtab_cmd.stroff); |
| 688 | } |
| 689 | |
| 690 | fn writeLoadCommands(macho_file: *MachO) error{ AlreadyReported, OutOfMemory }!struct { usize, usize } { |
| 691 | const gpa = macho_file.base.comp.gpa; |
| 692 | const needed_size = load_commands.calcLoadCommandsSizeObject(macho_file); |
| 693 | const buffer = try gpa.alloc(u8, needed_size); |
| 694 | defer gpa.free(buffer); |
| 695 | |
| 696 | var writer: Writer = .fixed(buffer); |
| 697 | |
| 698 | var ncmds: usize = 0; |
| 699 | |
| 700 | // Segment and section load commands |
| 701 | { |
| 702 | assert(macho_file.segments.items.len == 1); |
| 703 | const seg = macho_file.segments.items[0]; |
| 704 | writer.writeStruct(seg, .little) catch |err| switch (err) { |
| 705 | error.WriteFailed => unreachable, |
| 706 | }; |
| 707 | for (macho_file.sections.items(.header)) |header| { |
| 708 | writer.writeStruct(header, .little) catch |err| switch (err) { |
| 709 | error.WriteFailed => unreachable, |
| 710 | }; |
| 711 | } |
| 712 | ncmds += 1; |
| 713 | } |
| 714 | |
| 715 | writer.writeStruct(macho_file.data_in_code_cmd, .little) catch |err| switch (err) { |
| 716 | error.WriteFailed => unreachable, |
| 717 | }; |
| 718 | ncmds += 1; |
| 719 | writer.writeStruct(macho_file.symtab_cmd, .little) catch |err| switch (err) { |
| 720 | error.WriteFailed => unreachable, |
| 721 | }; |
| 722 | ncmds += 1; |
| 723 | writer.writeStruct(macho_file.dysymtab_cmd, .little) catch |err| switch (err) { |
| 724 | error.WriteFailed => unreachable, |
| 725 | }; |
| 726 | ncmds += 1; |
| 727 | |
| 728 | if (macho_file.platform.isBuildVersionCompatible()) { |
| 729 | load_commands.writeBuildVersionLC(macho_file.platform, macho_file.sdk_version, &writer) catch |err| switch (err) { |
| 730 | error.WriteFailed => unreachable, |
| 731 | }; |
| 732 | ncmds += 1; |
| 733 | } else { |
| 734 | load_commands.writeVersionMinLC(macho_file.platform, macho_file.sdk_version, &writer) catch |err| switch (err) { |
| 735 | error.WriteFailed => unreachable, |
| 736 | }; |
| 737 | ncmds += 1; |
| 738 | } |
| 739 | |
| 740 | assert(writer.end == needed_size); |
| 741 | |
| 742 | try macho_file.pwriteAll(buffer, @sizeOf(macho.mach_header_64)); |
| 743 | |
| 744 | return .{ ncmds, buffer.len }; |
| 745 | } |
| 746 | |
| 747 | fn writeHeader(macho_file: *MachO, ncmds: usize, sizeofcmds: usize) !void { |
| 748 | var header: macho.mach_header_64 = .{}; |
| 749 | header.filetype = macho.MH_OBJECT; |
| 750 | |
| 751 | const subsections_via_symbols = for (macho_file.objects.items) |index| { |
| 752 | const object = macho_file.getFile(index).?.object; |
| 753 | if (object.hasSubsections()) break true; |
| 754 | } else false; |
| 755 | if (subsections_via_symbols) { |
| 756 | header.flags |= macho.MH_SUBSECTIONS_VIA_SYMBOLS; |
| 757 | } |
| 758 | |
| 759 | switch (macho_file.getTarget().cpu.arch) { |
| 760 | .aarch64 => { |
| 761 | header.cputype = macho.CPU_TYPE_ARM64; |
| 762 | header.cpusubtype = macho.CPU_SUBTYPE_ARM_ALL; |
| 763 | }, |
| 764 | .x86_64 => { |
| 765 | header.cputype = macho.CPU_TYPE_X86_64; |
| 766 | header.cpusubtype = macho.CPU_SUBTYPE_X86_64_ALL; |
| 767 | }, |
| 768 | else => {}, |
| 769 | } |
| 770 | |
| 771 | header.ncmds = @intCast(ncmds); |
| 772 | header.sizeofcmds = @intCast(sizeofcmds); |
| 773 | |
| 774 | try macho_file.pwriteAll(mem.asBytes(&header), 0); |
| 775 | } |
| 776 | |
| 777 | const std = @import("std"); |
| 778 | const Path = std.Build.Cache.Path; |
| 779 | const assert = std.debug.assert; |
| 780 | const log = std.log.scoped(.link); |
| 781 | const macho = std.macho; |
| 782 | const math = std.math; |
| 783 | const mem = std.mem; |
| 784 | const state_log = std.log.scoped(.link_state); |
| 785 | const Writer = std.Io.Writer; |
| 786 | |
| 787 | const Archive = @import("Archive.zig"); |
| 788 | const Atom = @import("Atom.zig"); |
| 789 | const Compilation = @import("../../Compilation.zig"); |
| 790 | const File = @import("file.zig").File; |
| 791 | const MachO = @import("../MachO.zig"); |
| 792 | const Object = @import("Object.zig"); |
| 793 | const Symbol = @import("Symbol.zig"); |
| 794 | const build_options = @import("build_options"); |
| 795 | const eh_frame = @import("eh_frame.zig"); |
| 796 | const fat = @import("fat.zig"); |
| 797 | const link = @import("../../link.zig"); |
| 798 | const load_commands = @import("load_commands.zig"); |
| 799 | const trace = @import("../../tracy.zig").trace; |