authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2020-08-21 08:04:02+02:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2020-08-24 17:59:44+02:00
log2516db9645f3e8103ea6bff0b59d3a39fcce97a9
tree9a1153b96dea34570bc172c82059d24376c0d19e
parentc1ee9efb7c2a1589b14d6efeffb1f5e6c2d9b994

Specify path to dyld in Mach-O

This is required since an exec on macOS always has to link against libSystem.dylib. Signed-off-by: Jakub Konka <kubkon@jakubkonka.com>

3 files changed, 342 insertions(+), 48 deletions(-)

lib/std/macho.zig+23-1
...@@ -83,7 +83,7 @@ pub const symtab_command = extern struct {...@@ -83,7 +83,7 @@ pub const symtab_command = extern struct {
8383
84/// The linkedit_data_command contains the offsets and sizes of a blob84/// The linkedit_data_command contains the offsets and sizes of a blob
85/// of data in the __LINKEDIT segment.85/// of data in the __LINKEDIT segment.
86const linkedit_data_command = extern struct {86pub const linkedit_data_command = extern struct {
87 /// LC_CODE_SIGNATURE, LC_SEGMENT_SPLIT_INFO, LC_FUNCTION_STARTS, LC_DATA_IN_CODE, LC_DYLIB_CODE_SIGN_DRS or LC_LINKER_OPTIMIZATION_HINT.87 /// LC_CODE_SIGNATURE, LC_SEGMENT_SPLIT_INFO, LC_FUNCTION_STARTS, LC_DATA_IN_CODE, LC_DYLIB_CODE_SIGN_DRS or LC_LINKER_OPTIMIZATION_HINT.
88 cmd: u32,88 cmd: u32,
8989
...@@ -97,6 +97,28 @@ const linkedit_data_command = extern struct {...@@ -97,6 +97,28 @@ const linkedit_data_command = extern struct {
97 datasize: u32,97 datasize: u32,
98};98};
9999
100/// A program that uses a dynamic linker contains a dylinker_command to identify
101/// the name of the dynamic linker (LC_LOAD_DYLINKER). And a dynamic linker
102/// contains a dylinker_command to identify the dynamic linker (LC_ID_DYLINKER).
103/// A file can have at most one of these.
104/// This struct is also used for the LC_DYLD_ENVIRONMENT load command and contains
105/// string for dyld to treat like an environment variable.
106pub const dylinker_command = extern struct {
107 /// LC_ID_DYLINKER, LC_LOAD_DYLINKER, or LC_DYLD_ENVIRONMENT
108 cmd: u32,
109
110 /// includes pathname string
111 cmdsize: u32,
112
113 /// A variable length string in a load command is represented by an lc_str
114 /// union. The strings are stored just after the load command structure and
115 /// the offset is from the start of the load command structure. The size
116 /// of the string is reflected in the cmdsize field of the load command.
117 /// Once again any padded bytes to bring the cmdsize field to a multiple
118 /// of 4 bytes must be zero.
119 name: u32,
120};
121
100/// The segment load command indicates that a part of this file is to be122/// The segment load command indicates that a part of this file is to be
101/// mapped into the task's address space. The size of this segment in memory,123/// mapped into the task's address space. The size of this segment in memory,
102/// vmsize, maybe equal to or larger than the amount to map from this file,124/// vmsize, maybe equal to or larger than the amount to map from this file,
src-self-hosted/codegen.zig+56-1
...@@ -1427,7 +1427,62 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -1427,7 +1427,62 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
1427 else => return self.fail(inst.base.src, "TODO implement call for {}", .{self.target.cpu.arch}),1427 else => return self.fail(inst.base.src, "TODO implement call for {}", .{self.target.cpu.arch}),
1428 }1428 }
1429 } else if (self.bin_file.cast(link.File.MachO)) |macho_file| {1429 } else if (self.bin_file.cast(link.File.MachO)) |macho_file| {
1430 return self.fail(inst.base.src, "TODO implement codegen for call when linking with MachO", .{});1430 switch (arch) {
1431 // .x86_64 => {
1432 // for (info.args) |mc_arg, arg_i| {
1433 // const arg = inst.args[arg_i];
1434 // const arg_mcv = try self.resolveInst(inst.args[arg_i]);
1435 // // Here we do not use setRegOrMem even though the logic is similar, because
1436 // // the function call will move the stack pointer, so the offsets are different.
1437 // switch (mc_arg) {
1438 // .none => continue,
1439 // .register => |reg| {
1440 // try self.genSetReg(arg.src, reg, arg_mcv);
1441 // // TODO interact with the register allocator to mark the instruction as moved.
1442 // },
1443 // .stack_offset => {
1444 // // Here we need to emit instructions like this:
1445 // // mov qword ptr [rsp + stack_offset], x
1446 // return self.fail(inst.base.src, "TODO implement calling with parameters in memory", .{});
1447 // },
1448 // .ptr_stack_offset => {
1449 // return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_stack_offset arg", .{});
1450 // },
1451 // .ptr_embedded_in_code => {
1452 // return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_embedded_in_code arg", .{});
1453 // },
1454 // .undef => unreachable,
1455 // .immediate => unreachable,
1456 // .unreach => unreachable,
1457 // .dead => unreachable,
1458 // .embedded_in_code => unreachable,
1459 // .memory => unreachable,
1460 // .compare_flags_signed => unreachable,
1461 // .compare_flags_unsigned => unreachable,
1462 // }
1463 // }
1464
1465 // if (inst.func.cast(ir.Inst.Constant)) |func_inst| {
1466 // if (func_inst.val.cast(Value.Payload.Function)) |func_val| {
1467 // const func = func_val.func;
1468 // const got = &macho_file.segment_cmds.items[macho_file.seg_got_index.?];
1469 // const ptr_bytes: u64 = 8;
1470 // const got_addr = @intCast(u32, got.vmaddrs + func.owner_decl.link.macho.offset_table_index * ptr_bytes);
1471 // // 01 xx xx xx xx call [addr]
1472 // try self.code.ensureCapacity(self.code.items.len + 5);
1473 // self.code.appendSliceAssumeCapacity(&[1]u8{ 0x1 });
1474 // mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), got_addr);
1475 // } else {
1476 // return self.fail(inst.base.src, "TODO implement calling bitcasted functions", .{});
1477 // }
1478 // } else {
1479 // return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{});
1480 // }
1481 // },
1482 .x86_64 => return self.fail(inst.base.src, "TODO implement codegen for call when linking with MachO for x86_64 arch", .{}),
1483 .aarch64 => return self.fail(inst.base.src, "TODO implement codegen for call when linking with MachO for aarch64 arch", .{}),
1484 else => unreachable,
1485 }
1431 } else {1486 } else {
1432 unreachable;1487 unreachable;
1433 }1488 }
src-self-hosted/link/MachO.zig+263-46
...@@ -6,8 +6,11 @@ const assert = std.debug.assert;...@@ -6,8 +6,11 @@ const assert = std.debug.assert;
6const fs = std.fs;6const fs = std.fs;
7const log = std.log.scoped(.link);7const log = std.log.scoped(.link);
8const macho = std.macho;8const macho = std.macho;
9const codegen = @import("../codegen.zig");
9const math = std.math;10const math = std.math;
10const mem = std.mem;11const mem = std.mem;
12const trace = @import("../tracy.zig").trace;
13const Type = @import("../type.zig").Type;
1114
12const Module = @import("../Module.zig");15const Module = @import("../Module.zig");
13const link = @import("../link.zig");16const 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;
1720
18base: File,21base: File,
1922
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.
22segment_cmds: std.ArrayListUnmanaged(macho.segment_command_64) = std.ArrayListUnmanaged(macho.segment_command_64){},25commands: std.ArrayListUnmanaged(macho.load_command) = std.ArrayListUnmanaged(macho.load_command){},
26command_file_offset: ?u64 = null,
2327
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.
30segments: std.ArrayListUnmanaged(macho.segment_command_64) = std.ArrayListUnmanaged(macho.segment_command_64){},
26sections: std.ArrayListUnmanaged(macho.section_64) = std.ArrayListUnmanaged(macho.section_64){},31sections: std.ArrayListUnmanaged(macho.section_64) = std.ArrayListUnmanaged(macho.section_64){},
32segment_table_offset: ?u64 = null,
2733
34/// Entry point load command
35entry_point_cmd: ?macho.entry_point_command = null,
28entry_addr: ?u64 = null,36entry_addr: ?u64 = null,
2937
38/// Default VM start address set at 4GB
39vm_start_address: u64 = 0x100000000,
40
41seg_table_dirty: bool = false,
42
30error_flags: File.ErrorFlags = File.ErrorFlags{},43error_flags: File.ErrorFlags = File.ErrorFlags{},
3144
45/// `alloc_num / alloc_den` is the factor of padding when allocating.
46const alloc_num = 4;
47const alloc_den = 3;
48
49/// Default path to dyld
50const DEFAULT_DYLD_PATH: [*:0]const u8 = "/usr/lib/dyld";
51
32pub const TextBlock = struct {52pub 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.
82fn createFile(allocator: *Allocator, file: fs.File, options: link.Options) !MachO {102fn 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();
98112
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 }
115138
116 return self;139 try self.populateMissingMetadata();
117}
118140
119fn 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}
125143
126fn writeMachOHeader(self: *MachO) !void {144fn 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;
158176
159 // TODO consider other commands177 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;
163185
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}
170192
171pub fn flush(self: *MachO, module: *Module) !void {193pub fn flush(self: *MachO, module: *Module) !void {
172 // TODO implement flush194 // 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);
176198
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 }
180205
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 }
183208
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}
193251
194pub fn deinit(self: *MachO) void {252pub 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}
198257
199pub fn allocateDeclIndexes(self: *MachO, decl: *Module.Decl) !void {}258pub fn allocateDeclIndexes(self: *MachO, decl: *Module.Decl) !void {}
200259
201pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {}260pub 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}
202305
203pub fn updateDeclLineNumber(self: *MachO, module: *Module, decl: *const Module.Decl) !void {}306pub fn updateDeclLineNumber(self: *MachO, module: *Module, decl: *const Module.Decl) !void {}
204307
...@@ -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 {}
214pub fn getDeclVAddr(self: *MachO, decl: *const Module.Decl) u64 {317pub 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
321pub 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.
361fn 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
407fn 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
416fn 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
421fn 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
428fn 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}