authorgravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2026-07-25 01:16:19+02:00
committergravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2026-07-25 01:17:21+02:00
log72e09893686613bd65369faf9272171d272fbee3
treeaf35966b4a2a6fee13cad0aa755d2feb2ddc4bec
parent2ae38cbd2599af06beebc4ae9e3cdbbbc6cefc89
signaturebadge-check Signed by SSH key SHA256:7B/LJ7bpR1eX8aCXSr4mtd5M45VMPKcx9zY8e95b5QM

link.MachO: add LC_ENCRYPTION_INFO_64 for non-sim ios/tvos/visionos/watchos

closes https://codeberg.org/ziglang/zig/issues/36285

2 files changed, 45 insertions(+), 8 deletions(-)

src/link/MachO.zig+23-3
...@@ -24,6 +24,8 @@ dylibs: std.ArrayList(File.Index) = .empty,...@@ -24,6 +24,8 @@ dylibs: std.ArrayList(File.Index) = .empty,
2424
25segments: std.ArrayList(macho.segment_command_64) = .empty,25segments: std.ArrayList(macho.segment_command_64) = .empty,
26sections: std.MultiArrayList(Section) = .{},26sections: std.MultiArrayList(Section) = .{},
27/// Populated by `allocateSections`.
28header_size: ?u32 = null,
2729
28resolver: SymbolResolver = .{},30resolver: SymbolResolver = .{},
29/// This table will be populated after `scanRelocs` has run.31/// This table will be populated after `scanRelocs` has run.
...@@ -2209,13 +2211,14 @@ fn initSegments(self: *MachO) !void {...@@ -2209,13 +2211,14 @@ fn initSegments(self: *MachO) !void {
2209}2211}
22102212
2211fn allocateSections(self: *MachO) !void {2213fn allocateSections(self: *MachO) !void {
2212 const headerpad = try load_commands.calcMinHeaderPadSize(self);2214 const header_size = try load_commands.calcMinHeaderSize(self);
2215 self.header_size = header_size;
2213 var vmaddr: u64 = if (self.pagezero_seg_index) |index|2216 var vmaddr: u64 = if (self.pagezero_seg_index) |index|
2214 self.segments.items[index].vmaddr + self.segments.items[index].vmsize2217 self.segments.items[index].vmaddr + self.segments.items[index].vmsize
2215 else2218 else
2216 0;2219 0;
2217 vmaddr += headerpad;2220 vmaddr += header_size;
2218 var fileoff = headerpad;2221 var fileoff = header_size;
2219 var prev_seg_id: u8 = if (self.pagezero_seg_index) |index| index + 1 else 0;2222 var prev_seg_id: u8 = if (self.pagezero_seg_index) |index| index + 1 else 0;
22202223
2221 const page_size = self.getPageSize();2224 const page_size = self.getPageSize();
...@@ -2895,6 +2898,11 @@ fn writeLoadCommands(self: *MachO) !struct { usize, usize, u64 } {...@@ -2895,6 +2898,11 @@ fn writeLoadCommands(self: *MachO) !struct { usize, usize, u64 } {
2895 ncmds += 1;2898 ncmds += 1;
2896 }2899 }
28972900
2901 if (self.needsEncryptionInfo()) {
2902 try load_commands.writeEncryptionInfoLC(self, &writer);
2903 ncmds += 1;
2904 }
2905
2898 for (self.rpath_list) |rpath| {2906 for (self.rpath_list) |rpath| {
2899 try load_commands.writeRpathLC(rpath, &writer);2907 try load_commands.writeRpathLC(rpath, &writer);
2900 ncmds += 1;2908 ncmds += 1;
...@@ -5410,6 +5418,18 @@ pub fn alignPow(macho_file: *MachO, x: u32) error{AlreadyReported}!u32 {...@@ -5410,6 +5418,18 @@ pub fn alignPow(macho_file: *MachO, x: u32) error{AlreadyReported}!u32 {
5410 return result;5418 return result;
5411}5419}
54125420
5421pub fn needsEncryptionInfo(macho_file: *MachO) bool {
5422 const target = macho_file.getTarget();
5423 return switch (target.os.tag) {
5424 .ios,
5425 .tvos,
5426 .visionos,
5427 .watchos,
5428 => target.abi != .simulator,
5429 else => false,
5430 };
5431}
5432
5413/// Branch instruction has 26 bits immediate but is 4 byte aligned.5433/// Branch instruction has 26 bits immediate but is 4 byte aligned.
5414const jump_bits = @bitSizeOf(i28);5434const jump_bits = @bitSizeOf(i28);
5415const max_distance = (1 << (jump_bits - 1));5435const max_distance = (1 << (jump_bits - 1));
src/link/MachO/load_commands.zig+22-5
...@@ -62,6 +62,10 @@ pub fn calcLoadCommandsSize(macho_file: *MachO, assume_max_path_len: bool) !u32...@@ -62,6 +62,10 @@ pub fn calcLoadCommandsSize(macho_file: *MachO, assume_max_path_len: bool) !u32
62 assume_max_path_len,62 assume_max_path_len,
63 );63 );
64 }64 }
65 // LC_ENCRYPTION_INFO_64
66 if (macho_file.needsEncryptionInfo()) {
67 sizeofcmds += @sizeOf(macho.encryption_info_command_64);
68 }
65 // LC_RPATH69 // LC_RPATH
66 {70 {
67 for (macho_file.rpath_list) |rpath| {71 for (macho_file.rpath_list) |rpath| {
...@@ -163,23 +167,29 @@ pub fn calcLoadCommandsSizeObject(macho_file: *MachO) u32 {...@@ -163,23 +167,29 @@ pub fn calcLoadCommandsSizeObject(macho_file: *MachO) u32 {
163 return @as(u32, @intCast(sizeofcmds));167 return @as(u32, @intCast(sizeofcmds));
164}168}
165169
166pub fn calcMinHeaderPadSize(macho_file: *MachO) !u32 {170pub fn calcMinHeaderSize(macho_file: *MachO) !u32 {
167 var padding: u32 = (try calcLoadCommandsSize(macho_file, false)) +171 var padding: u32 = (try calcLoadCommandsSize(macho_file, false)) +
168 (macho_file.headerpad_size orelse MachO.default_headerpad_size);172 (macho_file.headerpad_size orelse MachO.default_headerpad_size);
169 log.debug("minimum requested headerpad size 0x{x}", .{padding + @sizeOf(macho.mach_header_64)});173 log.debug("minimum requested header + padding size 0x{x}", .{padding + @sizeOf(macho.mach_header_64)});
170174
171 if (macho_file.headerpad_max_install_names) {175 if (macho_file.headerpad_max_install_names) {
172 const min_headerpad_size: u32 = try calcLoadCommandsSize(macho_file, true);176 const min_headerpad_size: u32 = try calcLoadCommandsSize(macho_file, true);
173 log.debug("headerpad_max_install_names minimum headerpad size 0x{x}", .{177 log.debug("headerpad_max_install_names minimum header + padding size 0x{x}", .{
174 min_headerpad_size + @sizeOf(macho.mach_header_64),178 min_headerpad_size + @sizeOf(macho.mach_header_64),
175 });179 });
176 padding = @max(padding, min_headerpad_size);180 padding = @max(padding, min_headerpad_size);
177 }181 }
178182
179 const offset = @sizeOf(macho.mach_header_64) + padding;183 const offset = @sizeOf(macho.mach_header_64) + padding;
180 log.debug("actual headerpad size 0x{x}", .{offset});184 log.debug("actual header + padding size 0x{x}", .{offset});
181185
182 return offset;186 // Encryption is done at page granularity, so if the output needs a load
187 // command for encryption info, ensure that the header + load commands have
188 // at least one full, unencrypted page.
189 return if (macho_file.needsEncryptionInfo())
190 mem.alignForward(u32, offset, macho_file.getPageSize())
191 else
192 offset;
183}193}
184194
185pub fn writeDylinkerLC(writer: *Writer) !void {195pub fn writeDylinkerLC(writer: *Writer) !void {
...@@ -260,6 +270,13 @@ pub fn writeDylibIdLC(macho_file: *MachO, writer: *Writer) !void {...@@ -260,6 +270,13 @@ pub fn writeDylibIdLC(macho_file: *MachO, writer: *Writer) !void {
260 }, writer);270 }, writer);
261}271}
262272
273pub fn writeEncryptionInfoLC(macho_file: *MachO, writer: *Writer) !void {
274 try writer.writeAll(mem.asBytes(&macho.encryption_info_command_64{
275 .cryptoff = macho_file.header_size.?,
276 .cryptsize = @as(u32, @intCast(macho_file.getTextSegment().filesize)) - macho_file.header_size.?,
277 }));
278}
279
263pub fn writeRpathLC(rpath: []const u8, writer: *Writer) !void {280pub fn writeRpathLC(rpath: []const u8, writer: *Writer) !void {
264 const rpath_len = rpath.len + 1;281 const rpath_len = rpath.len + 1;
265 const cmdsize = @as(u32, @intCast(mem.alignForward(282 const cmdsize = @as(u32, @intCast(mem.alignForward(