From 2ae38cbd2599af06beebc4ae9e3cdbbbc6cefc89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Sat, 25 Jul 2026 01:15:36 +0200 Subject: [PATCH 1/2] std.macho: add encryption_info_command[_64] --- lib/std/macho.zig | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/lib/std/macho.zig b/lib/std/macho.zig index 9fdce9dd6605f450ab7eb9fee2b19b4f3787783d..48804cf7b1ccb51e8a37052686d0a9afe670d116 100644 --- a/lib/std/macho.zig +++ b/lib/std/macho.zig @@ -588,6 +588,25 @@ pub const rpath_command = extern struct { path: u32, }; +pub const encryption_info_command = extern struct { + cmd: LC = .ENCRYPTION_INFO, + cmdsize: u32 = @sizeOf(encryption_info_command), + + cryptoff: u32, + cryptsize: u32, + cryptid: u32 = 0, +}; + +pub const encryption_info_command_64 = extern struct { + cmd: LC = .ENCRYPTION_INFO_64, + cmdsize: u32 = @sizeOf(encryption_info_command_64), + + cryptoff: u32, + cryptsize: u32, + cryptid: u32 = 0, + _pad: u32 = 0, +}; + /// The segment load command indicates that a part of this file is to be /// mapped into the task's address space. The size of this segment in memory, /// vmsize, maybe equal to or larger than the amount to map from this file, -- 2.54.0 From 72e09893686613bd65369faf9272171d272fbee3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Sat, 25 Jul 2026 01:16:19 +0200 Subject: [PATCH 2/2] link.MachO: add LC_ENCRYPTION_INFO_64 for non-sim ios/tvos/visionos/watchos closes https://codeberg.org/ziglang/zig/issues/36285 --- src/link/MachO.zig | 26 +++++++++++++++++++++++--- src/link/MachO/load_commands.zig | 27 ++++++++++++++++++++++----- 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 543d803b11286b8a22155ba80d785012d9f49363..77441748d57ef2ad1823f01d2bad18c3b9c1a5e5 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -24,6 +24,8 @@ dylibs: std.ArrayList(File.Index) = .empty, segments: std.ArrayList(macho.segment_command_64) = .empty, sections: std.MultiArrayList(Section) = .{}, +/// Populated by `allocateSections`. +header_size: ?u32 = null, resolver: SymbolResolver = .{}, /// This table will be populated after `scanRelocs` has run. @@ -2209,13 +2211,14 @@ fn initSegments(self: *MachO) !void { } fn allocateSections(self: *MachO) !void { - const headerpad = try load_commands.calcMinHeaderPadSize(self); + const header_size = try load_commands.calcMinHeaderSize(self); + self.header_size = header_size; var vmaddr: u64 = if (self.pagezero_seg_index) |index| self.segments.items[index].vmaddr + self.segments.items[index].vmsize else 0; - vmaddr += headerpad; - var fileoff = headerpad; + vmaddr += header_size; + var fileoff = header_size; var prev_seg_id: u8 = if (self.pagezero_seg_index) |index| index + 1 else 0; const page_size = self.getPageSize(); @@ -2895,6 +2898,11 @@ fn writeLoadCommands(self: *MachO) !struct { usize, usize, u64 } { ncmds += 1; } + if (self.needsEncryptionInfo()) { + try load_commands.writeEncryptionInfoLC(self, &writer); + ncmds += 1; + } + for (self.rpath_list) |rpath| { try load_commands.writeRpathLC(rpath, &writer); ncmds += 1; @@ -5410,6 +5418,18 @@ pub fn alignPow(macho_file: *MachO, x: u32) error{AlreadyReported}!u32 { return result; } +pub fn needsEncryptionInfo(macho_file: *MachO) bool { + const target = macho_file.getTarget(); + return switch (target.os.tag) { + .ios, + .tvos, + .visionos, + .watchos, + => target.abi != .simulator, + else => false, + }; +} + /// Branch instruction has 26 bits immediate but is 4 byte aligned. const jump_bits = @bitSizeOf(i28); const max_distance = (1 << (jump_bits - 1)); diff --git a/src/link/MachO/load_commands.zig b/src/link/MachO/load_commands.zig index ec556b16bb9a7a50ada47fe9740c25aea9fc775b..62bbc751cb58e82aee019f07065eabf1b8cf8917 100644 --- a/src/link/MachO/load_commands.zig +++ b/src/link/MachO/load_commands.zig @@ -62,6 +62,10 @@ pub fn calcLoadCommandsSize(macho_file: *MachO, assume_max_path_len: bool) !u32 assume_max_path_len, ); } + // LC_ENCRYPTION_INFO_64 + if (macho_file.needsEncryptionInfo()) { + sizeofcmds += @sizeOf(macho.encryption_info_command_64); + } // LC_RPATH { for (macho_file.rpath_list) |rpath| { @@ -163,23 +167,29 @@ pub fn calcLoadCommandsSizeObject(macho_file: *MachO) u32 { return @as(u32, @intCast(sizeofcmds)); } -pub fn calcMinHeaderPadSize(macho_file: *MachO) !u32 { +pub fn calcMinHeaderSize(macho_file: *MachO) !u32 { var padding: u32 = (try calcLoadCommandsSize(macho_file, false)) + (macho_file.headerpad_size orelse MachO.default_headerpad_size); - log.debug("minimum requested headerpad size 0x{x}", .{padding + @sizeOf(macho.mach_header_64)}); + log.debug("minimum requested header + padding size 0x{x}", .{padding + @sizeOf(macho.mach_header_64)}); if (macho_file.headerpad_max_install_names) { const min_headerpad_size: u32 = try calcLoadCommandsSize(macho_file, true); - log.debug("headerpad_max_install_names minimum headerpad size 0x{x}", .{ + log.debug("headerpad_max_install_names minimum header + padding size 0x{x}", .{ min_headerpad_size + @sizeOf(macho.mach_header_64), }); padding = @max(padding, min_headerpad_size); } const offset = @sizeOf(macho.mach_header_64) + padding; - log.debug("actual headerpad size 0x{x}", .{offset}); + log.debug("actual header + padding size 0x{x}", .{offset}); - return offset; + // Encryption is done at page granularity, so if the output needs a load + // command for encryption info, ensure that the header + load commands have + // at least one full, unencrypted page. + return if (macho_file.needsEncryptionInfo()) + mem.alignForward(u32, offset, macho_file.getPageSize()) + else + offset; } pub fn writeDylinkerLC(writer: *Writer) !void { @@ -260,6 +270,13 @@ pub fn writeDylibIdLC(macho_file: *MachO, writer: *Writer) !void { }, writer); } +pub fn writeEncryptionInfoLC(macho_file: *MachO, writer: *Writer) !void { + try writer.writeAll(mem.asBytes(&macho.encryption_info_command_64{ + .cryptoff = macho_file.header_size.?, + .cryptsize = @as(u32, @intCast(macho_file.getTextSegment().filesize)) - macho_file.header_size.?, + })); +} + pub fn writeRpathLC(rpath: []const u8, writer: *Writer) !void { const rpath_len = rpath.len + 1; const cmdsize = @as(u32, @intCast(mem.alignForward( -- 2.54.0