| ... | ... | @@ -60,6 +60,17 @@ pub const SearchStrategy = enum { |
| 60 | 60 | dylibs_first, |
| 61 | 61 | }; |
| 62 | 62 | |
| 63 | /// Mode of operation of the linker. |
| 64 | pub const Mode = enum { |
| 65 | /// Incremental mode will preallocate segments/sections and is compatible with |
| 66 | /// watch and HCS modes of operation. |
| 67 | incremental, |
| 68 | /// Zld mode will link relocatables in a traditional, one-shot |
| 69 | /// fashion (default for LLVM backend). It acts as a drop-in replacement for |
| 70 | /// LLD. |
| 71 | zld, |
| 72 | }; |
| 73 | |
| 63 | 74 | const Section = struct { |
| 64 | 75 | header: macho.section_64, |
| 65 | 76 | segment_index: u8, |
| ... | ... | @@ -98,10 +109,7 @@ d_sym: ?DebugSymbols = null, |
| 98 | 109 | /// For x86_64 that's 4KB, whereas for aarch64, that's 16KB. |
| 99 | 110 | page_size: u16, |
| 100 | 111 | |
| 101 | | /// Mode of operation: incremental - will preallocate segments/sections and is compatible with |
| 102 | | /// watch and HCS modes of operation; one_shot - will link relocatables in a traditional, one-shot |
| 103 | | /// fashion (default for LLVM backend). |
| 104 | | mode: enum { incremental, one_shot }, |
| 112 | mode: Mode, |
| 105 | 113 | |
| 106 | 114 | dyld_info_cmd: macho.dyld_info_command = .{}, |
| 107 | 115 | symtab_cmd: macho.symtab_command = .{}, |
| ... | ... | @@ -314,33 +322,42 @@ pub const default_headerpad_size: u32 = 0x1000; |
| 314 | 322 | pub fn openPath(allocator: Allocator, options: link.Options) !*MachO { |
| 315 | 323 | assert(options.target.ofmt == .macho); |
| 316 | 324 | |
| 317 | | if (options.emit == null or options.module == null) { |
| 325 | if (options.emit == null) { |
| 318 | 326 | return createEmpty(allocator, options); |
| 319 | 327 | } |
| 320 | 328 | |
| 321 | 329 | const emit = options.emit.?; |
| 330 | const mode: Mode = mode: { |
| 331 | if (options.use_llvm or options.module == null or options.cache_mode == .whole) |
| 332 | break :mode .zld; |
| 333 | break :mode .incremental; |
| 334 | }; |
| 335 | const sub_path = if (mode == .zld) blk: { |
| 336 | if (options.module == null) { |
| 337 | // No point in opening a file, we would not write anything to it. |
| 338 | // Initialize with empty. |
| 339 | return createEmpty(allocator, options); |
| 340 | } |
| 341 | // Open a temporary object file, not the final output file because we |
| 342 | // want to link with LLD. |
| 343 | break :blk try std.fmt.allocPrint(allocator, "{s}{s}", .{ |
| 344 | emit.sub_path, options.target.ofmt.fileExt(options.target.cpu.arch), |
| 345 | }); |
| 346 | } else emit.sub_path; |
| 347 | errdefer if (mode == .zld) allocator.free(sub_path); |
| 348 | |
| 322 | 349 | const self = try createEmpty(allocator, options); |
| 323 | | errdefer { |
| 324 | | self.base.file = null; |
| 325 | | self.base.destroy(); |
| 326 | | } |
| 350 | errdefer self.base.destroy(); |
| 327 | 351 | |
| 328 | | if (build_options.have_llvm and options.use_llvm and options.module != null) { |
| 352 | if (mode == .zld) { |
| 329 | 353 | // TODO this intermediary_basename isn't enough; in the case of `zig build-exe`, |
| 330 | 354 | // we also want to put the intermediary object file in the cache while the |
| 331 | 355 | // main emit directory is the cwd. |
| 332 | | self.base.intermediary_basename = try std.fmt.allocPrint(allocator, "{s}{s}", .{ |
| 333 | | emit.sub_path, options.target.ofmt.fileExt(options.target.cpu.arch), |
| 334 | | }); |
| 356 | self.base.intermediary_basename = sub_path; |
| 357 | return self; |
| 335 | 358 | } |
| 336 | 359 | |
| 337 | | if (self.base.intermediary_basename != null) switch (options.output_mode) { |
| 338 | | .Obj => return self, |
| 339 | | .Lib => if (options.link_mode == .Static) return self, |
| 340 | | else => {}, |
| 341 | | }; |
| 342 | | |
| 343 | | const file = try emit.directory.handle.createFile(emit.sub_path, .{ |
| 360 | const file = try emit.directory.handle.createFile(sub_path, .{ |
| 344 | 361 | .truncate = false, |
| 345 | 362 | .read = true, |
| 346 | 363 | .mode = link.determineMode(options), |
| ... | ... | @@ -348,23 +365,21 @@ pub fn openPath(allocator: Allocator, options: link.Options) !*MachO { |
| 348 | 365 | errdefer file.close(); |
| 349 | 366 | self.base.file = file; |
| 350 | 367 | |
| 351 | | if (self.mode == .one_shot) return self; |
| 352 | | |
| 353 | 368 | if (!options.strip and options.module != null) { |
| 354 | 369 | // Create dSYM bundle. |
| 355 | | log.debug("creating {s}.dSYM bundle", .{emit.sub_path}); |
| 370 | log.debug("creating {s}.dSYM bundle", .{sub_path}); |
| 356 | 371 | |
| 357 | 372 | const d_sym_path = try fmt.allocPrint( |
| 358 | 373 | allocator, |
| 359 | 374 | "{s}.dSYM" ++ fs.path.sep_str ++ "Contents" ++ fs.path.sep_str ++ "Resources" ++ fs.path.sep_str ++ "DWARF", |
| 360 | | .{emit.sub_path}, |
| 375 | .{sub_path}, |
| 361 | 376 | ); |
| 362 | 377 | defer allocator.free(d_sym_path); |
| 363 | 378 | |
| 364 | 379 | var d_sym_bundle = try emit.directory.handle.makeOpenPath(d_sym_path, .{}); |
| 365 | 380 | defer d_sym_bundle.close(); |
| 366 | 381 | |
| 367 | | const d_sym_file = try d_sym_bundle.createFile(emit.sub_path, .{ |
| 382 | const d_sym_file = try d_sym_bundle.createFile(sub_path, .{ |
| 368 | 383 | .truncate = false, |
| 369 | 384 | .read = true, |
| 370 | 385 | }); |
| ... | ... | @@ -413,7 +428,7 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*MachO { |
| 413 | 428 | }, |
| 414 | 429 | .page_size = page_size, |
| 415 | 430 | .mode = if (use_llvm or options.module == null or options.cache_mode == .whole) |
| 416 | | .one_shot |
| 431 | .zld |
| 417 | 432 | else |
| 418 | 433 | .incremental, |
| 419 | 434 | }; |
| ... | ... | @@ -447,7 +462,7 @@ pub fn flush(self: *MachO, comp: *Compilation, prog_node: *std.Progress.Node) li |
| 447 | 462 | } |
| 448 | 463 | |
| 449 | 464 | switch (self.mode) { |
| 450 | | .one_shot => return zld.linkWithZld(self, comp, prog_node), |
| 465 | .zld => return zld.linkWithZld(self, comp, prog_node), |
| 451 | 466 | .incremental => return self.flushModule(comp, prog_node), |
| 452 | 467 | } |
| 453 | 468 | } |
| ... | ... | @@ -662,6 +677,8 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No |
| 662 | 677 | |
| 663 | 678 | if (codesig) |*csig| { |
| 664 | 679 | try self.writeCodeSignature(comp, csig); // code signing always comes last |
| 680 | const emit = self.base.options.emit.?; |
| 681 | try invalidateKernelCache(emit.directory.handle, emit.sub_path); |
| 665 | 682 | } |
| 666 | 683 | |
| 667 | 684 | if (self.d_sym) |*d_sym| { |
| ... | ... | @@ -691,6 +708,21 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No |
| 691 | 708 | |
| 692 | 709 | self.cold_start = false; |
| 693 | 710 | } |
| 711 | |
| 712 | /// XNU starting with Big Sur running on arm64 is caching inodes of running binaries. |
| 713 | /// Any change to the binary will effectively invalidate the kernel's cache |
| 714 | /// resulting in a SIGKILL on each subsequent run. Since when doing incremental |
| 715 | /// linking we're modifying a binary in-place, this will end up with the kernel |
| 716 | /// killing it on every subsequent run. To circumvent it, we will copy the file |
| 717 | /// into a new inode, remove the original file, and rename the copy to match |
| 718 | /// the original file. This is super messy, but there doesn't seem any other |
| 719 | /// way to please the XNU. |
| 720 | pub fn invalidateKernelCache(dir: std.fs.Dir, sub_path: []const u8) !void { |
| 721 | if (comptime builtin.target.isDarwin() and builtin.target.cpu.arch == .aarch64) { |
| 722 | try dir.copyFile(sub_path, dir, sub_path, .{}); |
| 723 | } |
| 724 | } |
| 725 | |
| 694 | 726 | inline fn conformUuid(out: *[Md5.digest_length]u8) void { |
| 695 | 727 | // LC_UUID uuids should conform to RFC 4122 UUID version 4 & UUID version 5 formats |
| 696 | 728 | out[6] = (out[6] & 0x0F) | (3 << 4); |