diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2022-08-04 14:24:00 -0700 |
|---|---|---|
| committer | Andrew Kelley <andrew@ziglang.org> | 2022-08-04 14:24:00 -0700 |
| commit | ba70eee8bb81da0d44982a84395aee660635e5ba (patch) | |
| tree | 40f4ef036ab60572f9b19e94dc84415993ed3fa8 /lib/std | |
| parent | a3045b8abbba896da34a02266f2be89dd6c90ecc (diff) | |
| parent | 616f65df750f53e6334cc5ed2c8f4b5668d573f2 (diff) | |
| download | zig-ba70eee8bb81da0d44982a84395aee660635e5ba.tar.gz zig-ba70eee8bb81da0d44982a84395aee660635e5ba.zip | |
Merge remote-tracking branch 'origin/master' into llvm15
Diffstat (limited to 'lib/std')
| -rw-r--r-- | lib/std/build/CheckObjectStep.zig | 89 | ||||
| -rw-r--r-- | lib/std/builtin.zig | 11 | ||||
| -rw-r--r-- | lib/std/crypto/25519/ed25519.zig | 27 | ||||
| -rw-r--r-- | lib/std/fs.zig | 31 | ||||
| -rw-r--r-- | lib/std/fs/test.zig | 5 | ||||
| -rw-r--r-- | lib/std/macho.zig | 464 |
6 files changed, 165 insertions, 462 deletions
diff --git a/lib/std/build/CheckObjectStep.zig b/lib/std/build/CheckObjectStep.zig index 0525bbf034..cc0982ec08 100644 --- a/lib/std/build/CheckObjectStep.zig +++ b/lib/std/build/CheckObjectStep.zig @@ -283,7 +283,14 @@ fn make(step: *Step) !void { const gpa = self.builder.allocator; const src_path = self.source.getPath(self.builder); - const contents = try fs.cwd().readFileAlloc(gpa, src_path, self.max_bytes); + const contents = try fs.cwd().readFileAllocOptions( + gpa, + src_path, + self.max_bytes, + null, + @alignOf(u64), + null, + ); const output = switch (self.obj_format) { .macho => try MachODumper.parseAndDump(contents, .{ @@ -370,9 +377,10 @@ const Opts = struct { }; const MachODumper = struct { + const LoadCommandIterator = macho.LoadCommandIterator; const symtab_label = "symtab"; - fn parseAndDump(bytes: []const u8, opts: Opts) ![]const u8 { + fn parseAndDump(bytes: []align(@alignOf(u64)) const u8, opts: Opts) ![]const u8 { const gpa = opts.gpa orelse unreachable; // MachO dumper requires an allocator var stream = std.io.fixedBufferStream(bytes); const reader = stream.reader(); @@ -385,55 +393,54 @@ const MachODumper = struct { var output = std.ArrayList(u8).init(gpa); const writer = output.writer(); - var load_commands = std.ArrayList(macho.LoadCommand).init(gpa); - try load_commands.ensureTotalCapacity(hdr.ncmds); - - var sections = std.ArrayList(struct { seg: u16, sect: u16 }).init(gpa); - var imports = std.ArrayList(u16).init(gpa); - - var symtab_cmd: ?u16 = null; - var i: u16 = 0; - while (i < hdr.ncmds) : (i += 1) { - var cmd = try macho.LoadCommand.read(gpa, reader); - load_commands.appendAssumeCapacity(cmd); + var symtab: []const macho.nlist_64 = undefined; + var strtab: []const u8 = undefined; + var sections = std.ArrayList(macho.section_64).init(gpa); + var imports = std.ArrayList([]const u8).init(gpa); + var it = LoadCommandIterator{ + .ncmds = hdr.ncmds, + .buffer = bytes[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds], + }; + var i: usize = 0; + while (it.next()) |cmd| { switch (cmd.cmd()) { .SEGMENT_64 => { - const seg = cmd.segment; - for (seg.sections.items) |_, j| { - try sections.append(.{ .seg = i, .sect = @intCast(u16, j) }); + const seg = cmd.cast(macho.segment_command_64).?; + try sections.ensureUnusedCapacity(seg.nsects); + for (cmd.getSections()) |sect| { + sections.appendAssumeCapacity(sect); } }, - .SYMTAB => { - symtab_cmd = i; + .SYMTAB => if (opts.dump_symtab) { + const lc = cmd.cast(macho.symtab_command).?; + symtab = @ptrCast( + [*]const macho.nlist_64, + @alignCast(@alignOf(macho.nlist_64), &bytes[lc.symoff]), + )[0..lc.nsyms]; + strtab = bytes[lc.stroff..][0..lc.strsize]; }, .LOAD_DYLIB, .LOAD_WEAK_DYLIB, .REEXPORT_DYLIB, => { - try imports.append(i); + try imports.append(cmd.getDylibPathName()); }, else => {}, } try dumpLoadCommand(cmd, i, writer); try writer.writeByte('\n'); + + i += 1; } if (opts.dump_symtab) { - const cmd = load_commands.items[symtab_cmd.?].symtab; - try writer.writeAll(symtab_label ++ "\n"); - const strtab = bytes[cmd.stroff..][0..cmd.strsize]; - const raw_symtab = bytes[cmd.symoff..][0 .. cmd.nsyms * @sizeOf(macho.nlist_64)]; - const symtab = mem.bytesAsSlice(macho.nlist_64, raw_symtab); - for (symtab) |sym| { if (sym.stab()) continue; const sym_name = mem.sliceTo(@ptrCast([*:0]const u8, strtab.ptr + sym.n_strx), 0); if (sym.sect()) { - const map = sections.items[sym.n_sect - 1]; - const seg = load_commands.items[map.seg].segment; - const sect = seg.sections.items[map.sect]; + const sect = sections.items[sym.n_sect - 1]; try writer.print("{x} ({s},{s})", .{ sym.n_value, sect.segName(), @@ -455,9 +462,7 @@ const MachODumper = struct { break :blk "flat lookup"; unreachable; } - const import_id = imports.items[@bitCast(u16, ordinal) - 1]; - const import = load_commands.items[import_id].dylib; - const full_path = mem.sliceTo(import.data, 0); + const full_path = imports.items[@bitCast(u16, ordinal) - 1]; const basename = fs.path.basename(full_path); assert(basename.len > 0); const ext = mem.lastIndexOfScalar(u8, basename, '.') orelse basename.len; @@ -481,7 +486,7 @@ const MachODumper = struct { return output.toOwnedSlice(); } - fn dumpLoadCommand(lc: macho.LoadCommand, index: u16, writer: anytype) !void { + fn dumpLoadCommand(lc: macho.LoadCommandIterator.LoadCommand, index: usize, writer: anytype) !void { // print header first try writer.print( \\LC {d} @@ -491,8 +496,7 @@ const MachODumper = struct { switch (lc.cmd()) { .SEGMENT_64 => { - // TODO dump section headers - const seg = lc.segment.inner; + const seg = lc.cast(macho.segment_command_64).?; try writer.writeByte('\n'); try writer.print( \\segname {s} @@ -508,7 +512,7 @@ const MachODumper = struct { seg.filesize, }); - for (lc.segment.sections.items) |sect| { + for (lc.getSections()) |sect| { try writer.writeByte('\n'); try writer.print( \\sectname {s} @@ -531,7 +535,7 @@ const MachODumper = struct { .LOAD_WEAK_DYLIB, .REEXPORT_DYLIB, => { - const dylib = lc.dylib.inner.dylib; + const dylib = lc.cast(macho.dylib_command).?; try writer.writeByte('\n'); try writer.print( \\name {s} @@ -539,19 +543,20 @@ const MachODumper = struct { \\current version {x} \\compatibility version {x} , .{ - mem.sliceTo(lc.dylib.data, 0), - dylib.timestamp, - dylib.current_version, - dylib.compatibility_version, + lc.getDylibPathName(), + dylib.dylib.timestamp, + dylib.dylib.current_version, + dylib.dylib.compatibility_version, }); }, .MAIN => { + const main = lc.cast(macho.entry_point_command).?; try writer.writeByte('\n'); try writer.print( \\entryoff {x} \\stacksize {x} - , .{ lc.main.entryoff, lc.main.stacksize }); + , .{ main.entryoff, main.stacksize }); }, .RPATH => { @@ -559,7 +564,7 @@ const MachODumper = struct { try writer.print( \\path {s} , .{ - mem.sliceTo(lc.rpath.data, 0), + lc.getRpathPathName(), }); }, diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index c38eb543ed..047c65439c 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -846,6 +846,17 @@ pub fn default_panic(msg: []const u8, error_return_trace: ?*StackTrace) noreturn } } +pub fn checkNonScalarSentinel(expected: anytype, actual: @TypeOf(expected)) void { + if (!std.meta.eql(expected, actual)) { + panicSentinelMismatch(expected, actual); + } +} + +pub fn panicSentinelMismatch(expected: anytype, actual: @TypeOf(expected)) noreturn { + @setCold(true); + std.debug.panic("sentinel mismatch: expected {any}, found {any}", .{ expected, actual }); +} + pub fn panicUnwrapError(st: ?*StackTrace, err: anyerror) noreturn { @setCold(true); std.debug.panicExtra(st, "attempt to unwrap error: {s}", .{@errorName(err)}); diff --git a/lib/std/crypto/25519/ed25519.zig b/lib/std/crypto/25519/ed25519.zig index 83a6c2389e..2a7671863e 100644 --- a/lib/std/crypto/25519/ed25519.zig +++ b/lib/std/crypto/25519/ed25519.zig @@ -229,15 +229,14 @@ pub const Ed25519 = struct { blind_secret_key: BlindSecretKey, }; - /// Blind an existing key pair with a blinding seed. - pub fn blind(key_pair: Ed25519.KeyPair, blind_seed: [blind_seed_length]u8) !BlindKeyPair { + /// Blind an existing key pair with a blinding seed and a context. + pub fn blind(key_pair: Ed25519.KeyPair, blind_seed: [blind_seed_length]u8, ctx: []const u8) !BlindKeyPair { var h: [Sha512.digest_length]u8 = undefined; Sha512.hash(key_pair.secret_key[0..32], &h, .{}); Curve.scalar.clamp(h[0..32]); const scalar = Curve.scalar.reduce(h[0..32].*); - var blind_h: [Sha512.digest_length]u8 = undefined; - Sha512.hash(blind_seed[0..], &blind_h, .{}); + const blind_h = blindCtx(blind_seed, ctx); const blind_factor = Curve.scalar.reduce(blind_h[0..32].*); const blind_scalar = Curve.scalar.mul(scalar, blind_factor); @@ -259,9 +258,8 @@ pub const Ed25519 = struct { } /// Recover a public key from a blind version of it. - pub fn unblindPublicKey(blind_public_key: [public_length]u8, blind_seed: [blind_seed_length]u8) ![public_length]u8 { - var blind_h: [Sha512.digest_length]u8 = undefined; - Sha512.hash(&blind_seed, &blind_h, .{}); + pub fn unblindPublicKey(blind_public_key: [public_length]u8, blind_seed: [blind_seed_length]u8, ctx: []const u8) ![public_length]u8 { + const blind_h = blindCtx(blind_seed, ctx); const inv_blind_factor = Scalar.fromBytes(blind_h[0..32].*).invert().toBytes(); const public_key = try (try Curve.fromBytes(blind_public_key)).mul(inv_blind_factor); return public_key.toBytes(); @@ -297,6 +295,17 @@ pub const Ed25519 = struct { mem.copy(u8, sig[32..], s[0..]); return sig; } + + /// Compute a blind context from a blinding seed and a context. + fn blindCtx(blind_seed: [blind_seed_length]u8, ctx: []const u8) [Sha512.digest_length]u8 { + var blind_h: [Sha512.digest_length]u8 = undefined; + var hx = Sha512.init(.{}); + hx.update(&blind_seed); + hx.update(&[1]u8{0}); + hx.update(ctx); + hx.final(&blind_h); + return blind_h; + } }; }; @@ -458,7 +467,7 @@ test "ed25519 with blind keys" { crypto.random.bytes(&blind); // Blind the key pair - const blind_kp = try BlindKeySignatures.blind(kp, blind); + const blind_kp = try BlindKeySignatures.blind(kp, blind, "ctx"); // Sign a message and check that it can be verified with the blind public key const msg = "test"; @@ -466,6 +475,6 @@ test "ed25519 with blind keys" { try Ed25519.verify(sig, msg, blind_kp.blind_public_key); // Unblind the public key - const pk = try BlindKeySignatures.unblindPublicKey(blind_kp.blind_public_key, blind); + const pk = try BlindKeySignatures.unblindPublicKey(blind_kp.blind_public_key, blind, "ctx"); try std.testing.expectEqualSlices(u8, &pk, &kp.public_key); } diff --git a/lib/std/fs.zig b/lib/std/fs.zig index c96a118399..0968e16812 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -595,6 +595,19 @@ pub const IterableDir = struct { /// Memory such as file names referenced in this returned entry becomes invalid /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized. pub fn next(self: *Self) Error!?Entry { + return self.nextLinux() catch |err| switch (err) { + // To be consistent across platforms, iteration ends if the directory being iterated is deleted during iteration. + // This matches the behavior of non-Linux UNIX platforms. + error.DirNotFound => null, + else => |e| return e, + }; + } + + pub const ErrorLinux = error{DirNotFound} || IteratorError; + + /// Implementation of `next` that can return `error.DirNotFound` if the directory being + /// iterated was deleted during iteration (this error is Linux specific). + pub fn nextLinux(self: *Self) ErrorLinux!?Entry { start_over: while (true) { if (self.index >= self.end_index) { if (self.first_iter) { @@ -607,7 +620,7 @@ pub const IterableDir = struct { .BADF => unreachable, // Dir is invalid or was opened without iteration ability .FAULT => unreachable, .NOTDIR => unreachable, - .NOENT => return null, // The directory being iterated was deleted during iteration. + .NOENT => return error.DirNotFound, // The directory being iterated was deleted during iteration. .INVAL => return error.Unexpected, // Linux may in some cases return EINVAL when reading /proc/$PID/net. else => |err| return os.unexpectedErrno(err), } @@ -729,6 +742,20 @@ pub const IterableDir = struct { /// Memory such as file names referenced in this returned entry becomes invalid /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized. pub fn next(self: *Self) Error!?Entry { + return self.nextWasi() catch |err| switch (err) { + // To be consistent across platforms, iteration ends if the directory being iterated is deleted during iteration. + // This matches the behavior of non-Linux UNIX platforms. + error.DirNotFound => null, + else => |e| return e, + }; + } + + pub const ErrorWasi = error{DirNotFound} || IteratorError; + + /// Implementation of `next` that can return platform-dependent errors depending on the host platform. + /// When the host platform is Linux, `error.DirNotFound` can be returned if the directory being + /// iterated was deleted during iteration. + pub fn nextWasi(self: *Self) ErrorWasi!?Entry { // We intentinally use fd_readdir even when linked with libc, // since its implementation is exactly the same as below, // and we avoid the code complexity here. @@ -742,7 +769,7 @@ pub const IterableDir = struct { .FAULT => unreachable, .NOTDIR => unreachable, .INVAL => unreachable, - .NOENT => return null, // The directory being iterated was deleted during iteration. + .NOENT => return error.DirNotFound, // The directory being iterated was deleted during iteration. .NOTCAPABLE => return error.AccessDenied, else => |err| return os.unexpectedErrno(err), } diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index bedec7d4ad..538ce1bf5e 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -241,6 +241,11 @@ test "Dir.Iterator but dir is deleted during iteration" { // Now, when we try to iterate, the next call should return null immediately. const entry = try iterator.next(); try std.testing.expect(entry == null); + + // On Linux, we can opt-in to receiving a more specific error by calling `nextLinux` + if (builtin.os.tag == .linux) { + try std.testing.expectError(error.DirNotFound, iterator.nextLinux()); + } } fn entryEql(lhs: IterableDir.Entry, rhs: IterableDir.Entry) bool { diff --git a/lib/std/macho.zig b/lib/std/macho.zig index cd4bfa37fb..1955a00334 100644 --- a/lib/std/macho.zig +++ b/lib/std/macho.zig @@ -780,7 +780,7 @@ pub const section_64 = extern struct { return parseName(§.segname); } - pub fn type_(sect: section_64) u8 { + pub fn @"type"(sect: section_64) u8 { return @truncate(u8, sect.flags & 0xff); } @@ -793,6 +793,11 @@ pub const section_64 = extern struct { return attr & S_ATTR_PURE_INSTRUCTIONS != 0 or attr & S_ATTR_SOME_INSTRUCTIONS != 0; } + pub fn isZerofill(sect: section_64) bool { + const tt = sect.@"type"(); + return tt == S_ZEROFILL or tt == S_GB_ZEROFILL or tt == S_THREAD_LOCAL_ZEROFILL; + } + pub fn isDebug(sect: section_64) bool { return sect.attrs() & S_ATTR_DEBUG != 0; } @@ -1835,429 +1840,70 @@ pub const data_in_code_entry = extern struct { kind: u16, }; -/// A Zig wrapper for all known MachO load commands. -/// Provides interface to read and write the load command data to a buffer. -pub const LoadCommand = union(enum) { - segment: SegmentCommand, - dyld_info_only: dyld_info_command, - symtab: symtab_command, - dysymtab: dysymtab_command, - dylinker: GenericCommandWithData(dylinker_command), - dylib: GenericCommandWithData(dylib_command), - main: entry_point_command, - version_min: version_min_command, - source_version: source_version_command, - build_version: GenericCommandWithData(build_version_command), - uuid: uuid_command, - linkedit_data: linkedit_data_command, - rpath: GenericCommandWithData(rpath_command), - unknown: GenericCommandWithData(load_command), - - pub fn read(allocator: Allocator, reader: anytype) !LoadCommand { - const header = try reader.readStruct(load_command); - var buffer = try allocator.alloc(u8, header.cmdsize); - defer allocator.free(buffer); - mem.copy(u8, buffer, mem.asBytes(&header)); - try reader.readNoEof(buffer[@sizeOf(load_command)..]); - var stream = io.fixedBufferStream(buffer); - - return switch (header.cmd) { - .SEGMENT_64 => LoadCommand{ - .segment = try SegmentCommand.read(allocator, stream.reader()), - }, - .DYLD_INFO, .DYLD_INFO_ONLY => LoadCommand{ - .dyld_info_only = try stream.reader().readStruct(dyld_info_command), - }, - .SYMTAB => LoadCommand{ - .symtab = try stream.reader().readStruct(symtab_command), - }, - .DYSYMTAB => LoadCommand{ - .dysymtab = try stream.reader().readStruct(dysymtab_command), - }, - .ID_DYLINKER, .LOAD_DYLINKER, .DYLD_ENVIRONMENT => LoadCommand{ - .dylinker = try GenericCommandWithData(dylinker_command).read(allocator, stream.reader()), - }, - .ID_DYLIB, .LOAD_WEAK_DYLIB, .LOAD_DYLIB, .REEXPORT_DYLIB => LoadCommand{ - .dylib = try GenericCommandWithData(dylib_command).read(allocator, stream.reader()), - }, - .MAIN => LoadCommand{ - .main = try stream.reader().readStruct(entry_point_command), - }, - .VERSION_MIN_MACOSX, .VERSION_MIN_IPHONEOS, .VERSION_MIN_WATCHOS, .VERSION_MIN_TVOS => LoadCommand{ - .version_min = try stream.reader().readStruct(version_min_command), - }, - .SOURCE_VERSION => LoadCommand{ - .source_version = try stream.reader().readStruct(source_version_command), - }, - .BUILD_VERSION => LoadCommand{ - .build_version = try GenericCommandWithData(build_version_command).read(allocator, stream.reader()), - }, - .UUID => LoadCommand{ - .uuid = try stream.reader().readStruct(uuid_command), - }, - .FUNCTION_STARTS, .DATA_IN_CODE, .CODE_SIGNATURE => LoadCommand{ - .linkedit_data = try stream.reader().readStruct(linkedit_data_command), - }, - .RPATH => LoadCommand{ - .rpath = try GenericCommandWithData(rpath_command).read(allocator, stream.reader()), - }, - else => LoadCommand{ - .unknown = try GenericCommandWithData(load_command).read(allocator, stream.reader()), - }, - }; - } - - pub fn write(self: LoadCommand, writer: anytype) !void { - return switch (self) { - .dyld_info_only => |x| writeStruct(x, writer), - .symtab => |x| writeStruct(x, writer), - .dysymtab => |x| writeStruct(x, writer), - .main => |x| writeStruct(x, writer), - .version_min => |x| writeStruct(x, writer), - .source_version => |x| writeStruct(x, writer), - .uuid => |x| writeStruct(x, writer), - .linkedit_data => |x| writeStruct(x, writer), - .segment => |x| x.write(writer), - .dylinker => |x| x.write(writer), - .dylib => |x| x.write(writer), - .rpath => |x| x.write(writer), - .build_version => |x| x.write(writer), - .unknown => |x| x.write(writer), - }; - } - - pub fn cmd(self: LoadCommand) LC { - return switch (self) { - .dyld_info_only => |x| x.cmd, - .symtab => |x| x.cmd, - .dysymtab => |x| x.cmd, - .main => |x| x.cmd, - .version_min => |x| x.cmd, - .source_version => |x| x.cmd, - .uuid => |x| x.cmd, - .linkedit_data => |x| x.cmd, - .segment => |x| x.inner.cmd, - .dylinker => |x| x.inner.cmd, - .dylib => |x| x.inner.cmd, - .rpath => |x| x.inner.cmd, - .build_version => |x| x.inner.cmd, - .unknown => |x| x.inner.cmd, - }; - } - - pub fn cmdsize(self: LoadCommand) u32 { - return switch (self) { - .dyld_info_only => |x| x.cmdsize, - .symtab => |x| x.cmdsize, - .dysymtab => |x| x.cmdsize, - .main => |x| x.cmdsize, - .version_min => |x| x.cmdsize, - .source_version => |x| x.cmdsize, - .linkedit_data => |x| x.cmdsize, - .uuid => |x| x.cmdsize, - .segment => |x| x.inner.cmdsize, - .dylinker => |x| x.inner.cmdsize, - .dylib => |x| x.inner.cmdsize, - .rpath => |x| x.inner.cmdsize, - .build_version => |x| x.inner.cmdsize, - .unknown => |x| x.inner.cmdsize, - }; - } - - pub fn deinit(self: *LoadCommand, allocator: Allocator) void { - return switch (self.*) { - .segment => |*x| x.deinit(allocator), - .dylinker => |*x| x.deinit(allocator), - .dylib => |*x| x.deinit(allocator), - .rpath => |*x| x.deinit(allocator), - .build_version => |*x| x.deinit(allocator), - .unknown => |*x| x.deinit(allocator), - else => {}, - }; - } - - fn writeStruct(command: anytype, writer: anytype) !void { - return writer.writeAll(mem.asBytes(&command)); - } - - pub fn eql(self: LoadCommand, other: LoadCommand) bool { - if (@as(meta.Tag(LoadCommand), self) != @as(meta.Tag(LoadCommand), other)) return false; - return switch (self) { - .dyld_info_only => |x| meta.eql(x, other.dyld_info_only), - .symtab => |x| meta.eql(x, other.symtab), - .dysymtab => |x| meta.eql(x, other.dysymtab), - .main => |x| meta.eql(x, other.main), - .version_min => |x| meta.eql(x, other.version_min), - .source_version => |x| meta.eql(x, other.source_version), - .build_version => |x| x.eql(other.build_version), - .uuid => |x| meta.eql(x, other.uuid), - .linkedit_data => |x| meta.eql(x, other.linkedit_data), - .segment => |x| x.eql(other.segment), - .dylinker => |x| x.eql(other.dylinker), - .dylib => |x| x.eql(other.dylib), - .rpath => |x| x.eql(other.rpath), - .unknown => |x| x.eql(other.unknown), - }; - } -}; - -/// A Zig wrapper for segment_command_64. -/// Encloses the extern struct together with a list of sections for this segment. -pub const SegmentCommand = struct { - inner: segment_command_64, - sections: std.ArrayListUnmanaged(section_64) = .{}, +pub const LoadCommandIterator = struct { + ncmds: usize, + buffer: []align(@alignOf(u64)) const u8, + index: usize = 0, - pub fn read(allocator: Allocator, reader: anytype) !SegmentCommand { - const inner = try reader.readStruct(segment_command_64); - var segment = SegmentCommand{ - .inner = inner, - }; - try segment.sections.ensureTotalCapacityPrecise(allocator, inner.nsects); + pub const LoadCommand = struct { + hdr: load_command, + data: []const u8, - var i: usize = 0; - while (i < inner.nsects) : (i += 1) { - const sect = try reader.readStruct(section_64); - segment.sections.appendAssumeCapacity(sect); + pub fn cmd(lc: LoadCommand) LC { + return lc.hdr.cmd; } - return segment; - } - - pub fn write(self: SegmentCommand, writer: anytype) !void { - try writer.writeAll(mem.asBytes(&self.inner)); - for (self.sections.items) |sect| { - try writer.writeAll(mem.asBytes(§)); - } - } - - pub fn deinit(self: *SegmentCommand, allocator: Allocator) void { - self.sections.deinit(allocator); - } - - pub fn eql(self: SegmentCommand, other: SegmentCommand) bool { - if (!meta.eql(self.inner, other.inner)) return false; - const lhs = self.sections.items; - const rhs = other.sections.items; - var i: usize = 0; - while (i < self.inner.nsects) : (i += 1) { - if (!meta.eql(lhs[i], rhs[i])) return false; + pub fn cmdsize(lc: LoadCommand) u32 { + return lc.hdr.cmdsize; } - return true; - } -}; - -pub fn emptyGenericCommandWithData(cmd: anytype) GenericCommandWithData(@TypeOf(cmd)) { - return .{ .inner = cmd }; -} -/// A Zig wrapper for a generic load command with variable-length data. -pub fn GenericCommandWithData(comptime Cmd: type) type { - return struct { - inner: Cmd, - /// This field remains undefined until `read` is called. - data: []u8 = undefined, - - const Self = @This(); - - pub fn read(allocator: Allocator, reader: anytype) !Self { - const inner = try reader.readStruct(Cmd); - var data = try allocator.alloc(u8, inner.cmdsize - @sizeOf(Cmd)); - errdefer allocator.free(data); - try reader.readNoEof(data); - return Self{ - .inner = inner, - .data = data, - }; + pub fn cast(lc: LoadCommand, comptime Cmd: type) ?Cmd { + if (lc.data.len < @sizeOf(Cmd)) return null; + return @ptrCast(*const Cmd, @alignCast(@alignOf(Cmd), &lc.data[0])).*; } - pub fn write(self: Self, writer: anytype) !void { - try writer.writeAll(mem.asBytes(&self.inner)); - try writer.writeAll(self.data); + /// Asserts LoadCommand is of type segment_command_64. + pub fn getSections(lc: LoadCommand) []const section_64 { + const segment_lc = lc.cast(segment_command_64).?; + if (segment_lc.nsects == 0) return &[0]section_64{}; + const data = lc.data[@sizeOf(segment_command_64)..]; + const sections = @ptrCast( + [*]const section_64, + @alignCast(@alignOf(section_64), &data[0]), + )[0..segment_lc.nsects]; + return sections; } - pub fn deinit(self: *Self, allocator: Allocator) void { - allocator.free(self.data); + /// Asserts LoadCommand is of type dylib_command. + pub fn getDylibPathName(lc: LoadCommand) []const u8 { + const dylib_lc = lc.cast(dylib_command).?; + const data = lc.data[dylib_lc.dylib.name..]; + return mem.sliceTo(data, 0); } - pub fn eql(self: Self, other: Self) bool { - if (!meta.eql(self.inner, other.inner)) return false; - return mem.eql(u8, self.data, other.data); + /// Asserts LoadCommand is of type rpath_command. + pub fn getRpathPathName(lc: LoadCommand) []const u8 { + const rpath_lc = lc.cast(rpath_command).?; + const data = lc.data[rpath_lc.path..]; + return mem.sliceTo(data, 0); } }; -} - -pub fn createLoadDylibCommand( - allocator: Allocator, - cmd_id: LC, - name: []const u8, - timestamp: u32, - current_version: u32, - compatibility_version: u32, -) !GenericCommandWithData(dylib_command) { - assert(cmd_id == .LOAD_DYLIB or cmd_id == .LOAD_WEAK_DYLIB or cmd_id == .REEXPORT_DYLIB or cmd_id == .ID_DYLIB); - const cmdsize = @intCast(u32, mem.alignForwardGeneric( - u64, - @sizeOf(dylib_command) + name.len + 1, // +1 for nul - @sizeOf(u64), - )); - - var dylib_cmd = emptyGenericCommandWithData(dylib_command{ - .cmd = cmd_id, - .cmdsize = cmdsize, - .dylib = .{ - .name = @sizeOf(dylib_command), - .timestamp = timestamp, - .current_version = current_version, - .compatibility_version = compatibility_version, - }, - }); - dylib_cmd.data = try allocator.alloc(u8, cmdsize - dylib_cmd.inner.dylib.name); - - mem.set(u8, dylib_cmd.data, 0); - mem.copy(u8, dylib_cmd.data, name); - - return dylib_cmd; -} - -fn testRead(allocator: Allocator, buffer: []const u8, expected: anytype) !void { - var stream = io.fixedBufferStream(buffer); - var given = try LoadCommand.read(allocator, stream.reader()); - defer given.deinit(allocator); - try testing.expect(expected.eql(given)); -} - -fn testWrite(buffer: []u8, cmd: LoadCommand, expected: []const u8) !void { - var stream = io.fixedBufferStream(buffer); - try cmd.write(stream.writer()); - try testing.expect(mem.eql(u8, expected, buffer[0..expected.len])); -} - -fn makeStaticString(bytes: []const u8) [16]u8 { - var buf = [_]u8{0} ** 16; - assert(bytes.len <= buf.len); - mem.copy(u8, &buf, bytes); - return buf; -} - -test "read-write segment command" { - // TODO compiling for macOS from big-endian arch - if (builtin.target.cpu.arch.endian() != .Little) return error.SkipZigTest; - - var gpa = testing.allocator; - const in_buffer = &[_]u8{ - 0x19, 0x00, 0x00, 0x00, // cmd - 0x98, 0x00, 0x00, 0x00, // cmdsize - 0x5f, 0x5f, 0x54, 0x45, 0x58, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // segname - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // vmaddr - 0x00, 0x80, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, // vmsize - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // fileoff - 0x00, 0x80, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, // filesize - 0x07, 0x00, 0x00, 0x00, // maxprot - 0x05, 0x00, 0x00, 0x00, // initprot - 0x01, 0x00, 0x00, 0x00, // nsects - 0x00, 0x00, 0x00, 0x00, // flags - 0x5f, 0x5f, 0x74, 0x65, 0x78, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // sectname - 0x5f, 0x5f, 0x54, 0x45, 0x58, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // segname - 0x00, 0x40, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // address - 0xc0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // size - 0x00, 0x40, 0x00, 0x00, // offset - 0x02, 0x00, 0x00, 0x00, // alignment - 0x00, 0x00, 0x00, 0x00, // reloff - 0x00, 0x00, 0x00, 0x00, // nreloc - 0x00, 0x04, 0x00, 0x80, // flags - 0x00, 0x00, 0x00, 0x00, // reserved1 - 0x00, 0x00, 0x00, 0x00, // reserved2 - 0x00, 0x00, 0x00, 0x00, // reserved3 - }; - var cmd = SegmentCommand{ - .inner = .{ - .cmdsize = 152, - .segname = makeStaticString("__TEXT"), - .vmaddr = 4294967296, - .vmsize = 294912, - .filesize = 294912, - .maxprot = PROT.READ | PROT.WRITE | PROT.EXEC, - .initprot = PROT.EXEC | PROT.READ, - .nsects = 1, - }, - }; - try cmd.sections.append(gpa, .{ - .sectname = makeStaticString("__text"), - .segname = makeStaticString("__TEXT"), - .addr = 4294983680, - .size = 448, - .offset = 16384, - .@"align" = 2, - .flags = S_REGULAR | S_ATTR_PURE_INSTRUCTIONS | S_ATTR_SOME_INSTRUCTIONS, - }); - defer cmd.deinit(gpa); - try testRead(gpa, in_buffer, LoadCommand{ .segment = cmd }); - - var out_buffer: [in_buffer.len]u8 = undefined; - try testWrite(&out_buffer, LoadCommand{ .segment = cmd }, in_buffer); -} -test "read-write generic command with data" { - // TODO compiling for macOS from big-endian arch - if (builtin.target.cpu.arch.endian() != .Little) return error.SkipZigTest; - - var gpa = testing.allocator; - const in_buffer = &[_]u8{ - 0x0c, 0x00, 0x00, 0x00, // cmd - 0x20, 0x00, 0x00, 0x00, // cmdsize - 0x18, 0x00, 0x00, 0x00, // name - 0x02, 0x00, 0x00, 0x00, // timestamp - 0x00, 0x00, 0x00, 0x00, // current_version - 0x00, 0x00, 0x00, 0x00, // compatibility_version - 0x2f, 0x75, 0x73, 0x72, 0x00, 0x00, 0x00, 0x00, // data - }; - var cmd = GenericCommandWithData(dylib_command){ - .inner = .{ - .cmd = .LOAD_DYLIB, - .cmdsize = 32, - .dylib = .{ - .name = 24, - .timestamp = 2, - .current_version = 0, - .compatibility_version = 0, - }, - }, - }; - cmd.data = try gpa.alloc(u8, 8); - defer gpa.free(cmd.data); - cmd.data[0] = 0x2f; - cmd.data[1] = 0x75; - cmd.data[2] = 0x73; - cmd.data[3] = 0x72; - cmd.data[4] = 0x0; - cmd.data[5] = 0x0; - cmd.data[6] = 0x0; - cmd.data[7] = 0x0; - try testRead(gpa, in_buffer, LoadCommand{ .dylib = cmd }); - - var out_buffer: [in_buffer.len]u8 = undefined; - try testWrite(&out_buffer, LoadCommand{ .dylib = cmd }, in_buffer); -} + pub fn next(it: *LoadCommandIterator) ?LoadCommand { + if (it.index >= it.ncmds) return null; -test "read-write C struct command" { - // TODO compiling for macOS from big-endian arch - if (builtin.target.cpu.arch.endian() != .Little) return error.SkipZigTest; + const hdr = @ptrCast( + *const load_command, + @alignCast(@alignOf(load_command), &it.buffer[0]), + ).*; + const cmd = LoadCommand{ + .hdr = hdr, + .data = it.buffer[0..hdr.cmdsize], + }; - var gpa = testing.allocator; - const in_buffer = &[_]u8{ - 0x28, 0x00, 0x00, 0x80, // cmd - 0x18, 0x00, 0x00, 0x00, // cmdsize - 0x04, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // entryoff - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // stacksize - }; - const cmd = .{ - .cmd = .MAIN, - .cmdsize = 24, - .entryoff = 16644, - .stacksize = 0, - }; - try testRead(gpa, in_buffer, LoadCommand{ .main = cmd }); + it.buffer = @alignCast(@alignOf(u64), it.buffer[hdr.cmdsize..]); + it.index += 1; - var out_buffer: [in_buffer.len]u8 = undefined; - try testWrite(&out_buffer, LoadCommand{ .main = cmd }, in_buffer); -} + return cmd; + } +}; |
