diff options
| author | Jakub Konka <kubkon@jakubkonka.com> | 2022-06-21 15:44:22 +0200 |
|---|---|---|
| committer | Jakub Konka <kubkon@jakubkonka.com> | 2022-06-21 15:44:24 +0200 |
| commit | 5fbdfb3f3477bc8ac70b828671a7f980e8a8ad10 (patch) | |
| tree | 5b325efa8ede38c5bf7bd45a48aed219d27ead91 /lib/std/build/CheckMachOStep.zig | |
| parent | 2d09540a636ab6ef2ca5087f18d55bbc259cd652 (diff) | |
| download | zig-5fbdfb3f3477bc8ac70b828671a7f980e8a8ad10.tar.gz zig-5fbdfb3f3477bc8ac70b828671a7f980e8a8ad10.zip | |
link-tests: add CheckMachOStep
CheckMachOStep specialises CheckFileStep into directed (surgical)
MachO file fuzzy searches. This will be the building block for
comprehensive MachO linker tests.
Diffstat (limited to 'lib/std/build/CheckMachOStep.zig')
| -rw-r--r-- | lib/std/build/CheckMachOStep.zig | 210 |
1 files changed, 210 insertions, 0 deletions
diff --git a/lib/std/build/CheckMachOStep.zig b/lib/std/build/CheckMachOStep.zig new file mode 100644 index 0000000000..1ac12a9749 --- /dev/null +++ b/lib/std/build/CheckMachOStep.zig @@ -0,0 +1,210 @@ +const std = @import("../std.zig"); +const build = std.build; +const Step = build.Step; +const Builder = build.Builder; +const fs = std.fs; +const macho = std.macho; +const mem = std.mem; + +const CheckMachOStep = @This(); + +pub const base_id = .check_macho; + +step: Step, +builder: *Builder, +source: build.FileSource, +max_bytes: usize = 20 * 1024 * 1024, +lc_checks: std.ArrayList(LCCheck), + +const LCCheck = struct { + // common to most LCs + cmd: macho.LC, + name: ?[]const u8 = null, + // LC.SEGMENT_64 specific + index: ?usize = null, + vaddr: ?u64 = null, + memsz: ?u64 = null, + offset: ?u64 = null, + filesz: ?u64 = null, + // LC.LOAD_DYLIB specific + timestamp: ?u64 = null, + current_version: ?u32 = null, + compat_version: ?u32 = null, +}; + +pub fn create(builder: *Builder, source: build.FileSource) *CheckMachOStep { + const gpa = builder.allocator; + const self = gpa.create(CheckMachOStep) catch unreachable; + self.* = CheckMachOStep{ + .builder = builder, + .step = Step.init(.check_file, "CheckMachO", gpa, make), + .source = source.dupe(builder), + .lc_checks = std.ArrayList(LCCheck).init(gpa), + }; + self.source.addStepDependencies(&self.step); + return self; +} + +pub fn checkLoadCommand(self: *CheckMachOStep, check: LCCheck) void { + self.lc_checks.append(.{ + .cmd = check.cmd, + .index = check.index, + .name = if (check.name) |name| self.builder.dupe(name) else null, + .vaddr = check.vaddr, + .memsz = check.memsz, + .offset = check.offset, + .filesz = check.filesz, + .timestamp = check.timestamp, + .current_version = check.current_version, + .compat_version = check.compat_version, + }) catch unreachable; +} + +fn make(step: *Step) !void { + const self = @fieldParentPtr(CheckMachOStep, "step", step); + + 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); + + // Parse the object file's header + var stream = std.io.fixedBufferStream(contents); + const reader = stream.reader(); + + const hdr = try reader.readStruct(macho.mach_header_64); + if (hdr.magic != macho.MH_MAGIC_64) { + return error.InvalidMagicNumber; + } + + var load_commands = std.ArrayList(macho.LoadCommand).init(gpa); + try load_commands.ensureTotalCapacity(hdr.ncmds); + + var i: u16 = 0; + while (i < hdr.ncmds) : (i += 1) { + var cmd = try macho.LoadCommand.read(gpa, reader); + load_commands.appendAssumeCapacity(cmd); + } + + outer: for (self.lc_checks.items) |ch| { + if (ch.index) |index| { + const lc = load_commands.items[index]; + try cmpLoadCommand(ch, lc); + } else { + for (load_commands.items) |lc| { + if (lc.cmd() == ch.cmd) { + try cmpLoadCommand(ch, lc); + continue :outer; + } + } else { + return err("LC not found", ch.cmd, ""); + } + } + } +} + +fn cmpLoadCommand(exp: LCCheck, given: macho.LoadCommand) error{TestFailed}!void { + if (exp.cmd != given.cmd()) { + return err("LC mismatch", exp.cmd, given.cmd()); + } + switch (exp.cmd) { + .SEGMENT_64 => { + const lc = given.segment.inner; + if (exp.name) |name| { + if (!mem.eql(u8, name, lc.segName())) { + return err("segment name mismatch", name, lc.segName()); + } + } + if (exp.vaddr) |vaddr| { + if (vaddr != lc.vmaddr) { + return err("segment VM address mismatch", vaddr, lc.vmaddr); + } + } + if (exp.memsz) |memsz| { + if (memsz != lc.vmsize) { + return err("segment VM size mismatch", memsz, lc.vmsize); + } + } + if (exp.offset) |offset| { + if (offset != lc.fileoff) { + return err("segment file offset mismatch", offset, lc.fileoff); + } + } + if (exp.filesz) |filesz| { + if (filesz != lc.filesize) { + return err("segment file size mismatch", filesz, lc.filesize); + } + } + }, + .ID_DYLIB, .LOAD_DYLIB => { + const lc = given.dylib; + if (exp.name) |name| { + if (!mem.eql(u8, name, mem.sliceTo(lc.data, 0))) { + return err("dylib path mismatch", name, mem.sliceTo(lc.data, 0)); + } + } + if (exp.timestamp) |ts| { + if (ts != lc.inner.dylib.timestamp) { + return err("timestamp mismatch", ts, lc.inner.dylib.timestamp); + } + } + if (exp.current_version) |cv| { + if (cv != lc.inner.dylib.current_version) { + return err("current version mismatch", cv, lc.inner.dylib.current_version); + } + } + if (exp.compat_version) |cv| { + if (cv != lc.inner.dylib.compatibility_version) { + return err("compatibility version mismatch", cv, lc.inner.dylib.compatibility_version); + } + } + }, + .RPATH => { + const lc = given.rpath; + if (exp.name) |name| { + if (!mem.eql(u8, name, mem.sliceTo(lc.data, 0))) { + return err("rpath path mismatch", name, mem.sliceTo(lc.data, 0)); + } + } + }, + else => @panic("TODO compare more load commands"), + } +} + +fn err(msg: []const u8, exp: anytype, giv: anytype) error{TestFailed} { + const fmt_specifier = if (comptime isString(@TypeOf(exp))) "{s}" else switch (@typeInfo(@TypeOf(exp))) { + .Int => "{x}", + .Float => "{d}", + else => "{any}", + }; + std.debug.print( + \\===================================== + \\{s} + \\ + \\======== Expected to find: ========== + \\ + ++ fmt_specifier ++ + \\ + \\======== But instead found: ========= + \\ + ++ fmt_specifier ++ + \\ + \\ + , .{ msg, exp, giv }); + return error.TestFailed; +} + +fn isString(comptime T: type) bool { + switch (@typeInfo(T)) { + .Array => return std.meta.Elem(T) == u8, + .Pointer => |pinfo| { + switch (pinfo.size) { + .Slice, .Many => return std.meta.Elem(T) == u8, + else => switch (@typeInfo(pinfo.child)) { + .Array => return isString(pinfo.child), + else => return false, + }, + } + }, + else => return false, + } +} |
