diff --git a/lib/std/build.zig b/lib/std/build.zig index 4582a0658d..57304242cd 100644 --- a/lib/std/build.zig +++ b/lib/std/build.zig @@ -24,7 +24,7 @@ pub const TranslateCStep = @import("build/TranslateCStep.zig"); pub const WriteFileStep = @import("build/WriteFileStep.zig"); pub const RunStep = @import("build/RunStep.zig"); pub const CheckFileStep = @import("build/CheckFileStep.zig"); -pub const CheckMachOStep = @import("build/CheckMachOStep.zig"); +pub const CheckObjectStep = @import("build/CheckObjectStep.zig"); pub const InstallRawStep = @import("build/InstallRawStep.zig"); pub const OptionsStep = @import("build/OptionsStep.zig"); @@ -1865,8 +1865,8 @@ pub const LibExeObjStep = struct { return run_step; } - pub fn checkMachO(self: *LibExeObjStep) *CheckMachOStep { - return CheckMachOStep.create(self.builder, self.getOutputSource()); + pub fn checkObject(self: *LibExeObjStep, obj_format: std.Target.ObjectFormat) *CheckObjectStep { + return CheckObjectStep.create(self.builder, self.getOutputSource(), obj_format); } pub fn setLinkerScriptPath(self: *LibExeObjStep, source: FileSource) void { @@ -3455,7 +3455,7 @@ pub const Step = struct { write_file, run, check_file, - check_macho, + check_object, install_raw, options, custom, diff --git a/lib/std/build/CheckMachOStep.zig b/lib/std/build/CheckObjectStep.zig similarity index 59% rename from lib/std/build/CheckMachOStep.zig rename to lib/std/build/CheckObjectStep.zig index a4cd35e95a..dae17b93db 100644 --- a/lib/std/build/CheckMachOStep.zig +++ b/lib/std/build/CheckObjectStep.zig @@ -5,13 +5,13 @@ const fs = std.fs; const macho = std.macho; const mem = std.mem; -const CheckMachOStep = @This(); +const CheckObjectStep = @This(); const Allocator = mem.Allocator; const Builder = build.Builder; const Step = build.Step; -pub const base_id = .check_macho; +pub const base_id = .check_obj; step: Step, builder: *Builder, @@ -19,15 +19,17 @@ source: build.FileSource, max_bytes: usize = 20 * 1024 * 1024, checks: std.ArrayList(Check), dump_symtab: bool = false, +obj_format: std.Target.ObjectFormat, -pub fn create(builder: *Builder, source: build.FileSource) *CheckMachOStep { +pub fn create(builder: *Builder, source: build.FileSource, obj_format: std.Target.ObjectFormat) *CheckObjectStep { const gpa = builder.allocator; - const self = gpa.create(CheckMachOStep) catch unreachable; - self.* = CheckMachOStep{ + const self = gpa.create(CheckObjectStep) catch unreachable; + self.* = .{ .builder = builder, - .step = Step.init(.check_file, "CheckMachO", gpa, make), + .step = Step.init(.check_file, "CheckObject", gpa, make), .source = source.dupe(builder), .checks = std.ArrayList(Check).init(gpa), + .obj_format = obj_format, }; self.source.addStepDependencies(&self.step); return self; @@ -84,19 +86,19 @@ const Check = struct { } }; -pub fn check(self: *CheckMachOStep, phrase: []const u8) void { +pub fn check(self: *CheckObjectStep, phrase: []const u8) void { var new_check = Check.create(self.builder); new_check.exactMatch(phrase); self.checks.append(new_check) catch unreachable; } -pub fn checkNext(self: *CheckMachOStep, phrase: []const u8) void { +pub fn checkNext(self: *CheckObjectStep, phrase: []const u8) void { assert(self.checks.items.len > 0); const last = &self.checks.items[self.checks.items.len - 1]; last.exactMatch(phrase); } -pub fn checkNextExtract(self: *CheckMachOStep, comptime phrase: []const u8) void { +pub fn checkNextExtract(self: *CheckObjectStep, comptime phrase: []const u8) void { assert(self.checks.items.len > 0); const matcher_start = comptime mem.indexOf(u8, phrase, "{") orelse @compileError("missing { } matcher"); @@ -106,12 +108,12 @@ pub fn checkNextExtract(self: *CheckMachOStep, comptime phrase: []const u8) void last.extractVar(phrase[0..matcher_start], phrase[matcher_start + 1 .. matcher_end]); } -pub fn checkInSymtab(self: *CheckMachOStep) void { +pub fn checkInSymtab(self: *CheckObjectStep) void { self.dump_symtab = true; self.check("symtab"); } -pub fn checkCompare(self: *CheckMachOStep, comptime phrase: []const u8, expected: anytype) void { +pub fn checkCompare(self: *CheckObjectStep, comptime phrase: []const u8, expected: anytype) void { comptime assert(phrase[0] == '{'); comptime assert(phrase[phrase.len - 1] == '}'); @@ -137,51 +139,22 @@ pub fn checkCompare(self: *CheckMachOStep, comptime phrase: []const u8, expected } fn make(step: *Step) !void { - const self = @fieldParentPtr(CheckMachOStep, "step", step); + const self = @fieldParentPtr(CheckObjectStep, "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 metadata = std.ArrayList(u8).init(gpa); - const writer = metadata.writer(); - - var symtab_cmd: ?macho.symtab_command = null; - var i: u16 = 0; - while (i < hdr.ncmds) : (i += 1) { - var cmd = try macho.LoadCommand.read(gpa, reader); - - if (self.dump_symtab and cmd.cmd() == .SYMTAB) { - symtab_cmd = cmd.symtab; - } - - try dumpLoadCommand(cmd, i, writer); - try writer.writeByte('\n'); - } - - if (symtab_cmd) |cmd| { - try writer.writeAll("symtab\n"); - const strtab = contents[cmd.stroff..][0..cmd.strsize]; - const symtab = @ptrCast( - [*]const macho.nlist_64, - @alignCast(@alignOf(macho.nlist_64), contents.ptr + cmd.symoff), - )[0..cmd.nsyms]; - - for (symtab) |sym| { - if (sym.stab()) continue; - const sym_name = mem.sliceTo(@ptrCast([*:0]const u8, strtab.ptr + sym.n_strx), 0); - try writer.print("{s} {x}\n", .{ sym_name, sym.n_value }); - } - } + const output = switch (self.obj_format) { + .macho => try MachODumper.parseAndDump(contents, .{ + .gpa = gpa, + .dump_symtab = self.dump_symtab, + }), + .elf => @panic("TODO elf parser"), + .coff => @panic("TODO coff parser"), + .wasm => @panic("TODO wasm parser"), + else => unreachable, + }; var vars = std.StringHashMap(u64).init(gpa); @@ -190,9 +163,9 @@ fn make(step: *Step) !void { switch (first_action) { .exact_match => |first| { - if (mem.indexOf(u8, metadata.items, first)) |index| { + if (mem.indexOf(u8, output, first)) |index| { // TODO backtrack to track current scope - var it = std.mem.tokenize(u8, metadata.items[index..], "\r\n"); + var it = std.mem.tokenize(u8, output[index..], "\r\n"); outer: for (chk.actions.items[1..]) |next_action| { switch (next_action) { @@ -263,69 +236,120 @@ fn make(step: *Step) !void { } } -fn dumpLoadCommand(lc: macho.LoadCommand, index: u16, writer: anytype) !void { - // print header first - try writer.print( - \\LC {d} - \\cmd {s} - \\cmdsize {d} - , .{ index, @tagName(lc.cmd()), lc.cmdsize() }); +const Opts = struct { + gpa: ?Allocator = null, + dump_symtab: bool = false, +}; - switch (lc.cmd()) { - .SEGMENT_64 => { - // TODO dump section headers - const seg = lc.segment.inner; +const MachODumper = struct { + fn parseAndDump(bytes: []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(); + + const hdr = try reader.readStruct(macho.mach_header_64); + if (hdr.magic != macho.MH_MAGIC_64) { + return error.InvalidMagicNumber; + } + + var output = std.ArrayList(u8).init(gpa); + const writer = output.writer(); + + var symtab_cmd: ?macho.symtab_command = null; + var i: u16 = 0; + while (i < hdr.ncmds) : (i += 1) { + var cmd = try macho.LoadCommand.read(gpa, reader); + + if (opts.dump_symtab and cmd.cmd() == .SYMTAB) { + symtab_cmd = cmd.symtab; + } + + try dumpLoadCommand(cmd, i, writer); try writer.writeByte('\n'); - try writer.print( - \\segname {s} - \\vmaddr {x} - \\vmsize {x} - \\fileoff {x} - \\filesz {x} - , .{ - seg.segName(), - seg.vmaddr, - seg.vmsize, - seg.fileoff, - seg.filesize, - }); - }, + } - .ID_DYLIB, - .LOAD_DYLIB, - => { - const dylib = lc.dylib.inner.dylib; - try writer.writeByte('\n'); - try writer.print( - \\path {s} - \\timestamp {d} - \\current version {x} - \\compatibility version {x} - , .{ - mem.sliceTo(lc.dylib.data, 0), - dylib.timestamp, - dylib.current_version, - dylib.compatibility_version, - }); - }, + if (symtab_cmd) |cmd| { + try writer.writeAll("symtab\n"); + const strtab = bytes[cmd.stroff..][0..cmd.strsize]; + const symtab = @ptrCast( + [*]const macho.nlist_64, + @alignCast(@alignOf(macho.nlist_64), bytes.ptr + cmd.symoff), + )[0..cmd.nsyms]; - .MAIN => { - try writer.writeByte('\n'); - try writer.print( - \\entryoff {x} - \\stacksize {x} - , .{ lc.main.entryoff, lc.main.stacksize }); - }, + for (symtab) |sym| { + if (sym.stab()) continue; + const sym_name = mem.sliceTo(@ptrCast([*:0]const u8, strtab.ptr + sym.n_strx), 0); + try writer.print("{s} {x}\n", .{ sym_name, sym.n_value }); + } + } - .RPATH => { - try writer.writeByte('\n'); - try writer.print( - \\path {s} - , .{ - mem.sliceTo(lc.rpath.data, 0), - }); - }, - - else => {}, + return output.toOwnedSlice(); } -} + + fn dumpLoadCommand(lc: macho.LoadCommand, index: u16, writer: anytype) !void { + // print header first + try writer.print( + \\LC {d} + \\cmd {s} + \\cmdsize {d} + , .{ index, @tagName(lc.cmd()), lc.cmdsize() }); + + switch (lc.cmd()) { + .SEGMENT_64 => { + // TODO dump section headers + const seg = lc.segment.inner; + try writer.writeByte('\n'); + try writer.print( + \\segname {s} + \\vmaddr {x} + \\vmsize {x} + \\fileoff {x} + \\filesz {x} + , .{ + seg.segName(), + seg.vmaddr, + seg.vmsize, + seg.fileoff, + seg.filesize, + }); + }, + + .ID_DYLIB, + .LOAD_DYLIB, + => { + const dylib = lc.dylib.inner.dylib; + try writer.writeByte('\n'); + try writer.print( + \\path {s} + \\timestamp {d} + \\current version {x} + \\compatibility version {x} + , .{ + mem.sliceTo(lc.dylib.data, 0), + dylib.timestamp, + dylib.current_version, + dylib.compatibility_version, + }); + }, + + .MAIN => { + try writer.writeByte('\n'); + try writer.print( + \\entryoff {x} + \\stacksize {x} + , .{ lc.main.entryoff, lc.main.stacksize }); + }, + + .RPATH => { + try writer.writeByte('\n'); + try writer.print( + \\path {s} + , .{ + mem.sliceTo(lc.rpath.data, 0), + }); + }, + + else => {}, + } + } +}; diff --git a/test/link.zig b/test/link.zig index e3dcfa1ec6..42ce12f73c 100644 --- a/test/link.zig +++ b/test/link.zig @@ -28,7 +28,7 @@ pub fn addCases(cases: *tests.StandaloneContext) void { }); if (builtin.os.tag == .macos) { - cases.addBuildFile("test/link/entry/build.zig", .{ + cases.addBuildFile("test/link/macho/entry/build.zig", .{ .build_modes = true, }); diff --git a/test/link/macho/dylib/build.zig b/test/link/macho/dylib/build.zig index 4f41e204ec..a613f02b4a 100644 --- a/test/link/macho/dylib/build.zig +++ b/test/link/macho/dylib/build.zig @@ -13,7 +13,7 @@ pub fn build(b: *Builder) void { dylib.linkLibC(); dylib.install(); - const check_dylib = dylib.checkMachO(); + const check_dylib = dylib.checkObject(.macho); check_dylib.check("cmd ID_DYLIB"); check_dylib.checkNext("path @rpath/liba.dylib"); check_dylib.checkNext("timestamp 2"); @@ -30,7 +30,7 @@ pub fn build(b: *Builder) void { exe.addLibraryPath(b.pathFromRoot("zig-out/lib/")); exe.addRPath(b.pathFromRoot("zig-out/lib")); - const check_exe = exe.checkMachO(); + const check_exe = exe.checkObject(.macho); check_exe.check("cmd LOAD_DYLIB"); check_exe.checkNext("path @rpath/liba.dylib"); check_exe.checkNext("timestamp 2"); diff --git a/test/link/macho/entry/build.zig b/test/link/macho/entry/build.zig index e8c0901b20..78225b0d73 100644 --- a/test/link/macho/entry/build.zig +++ b/test/link/macho/entry/build.zig @@ -13,7 +13,7 @@ pub fn build(b: *Builder) void { exe.linkLibC(); exe.entry_symbol_name = "_non_main"; - const check_exe = exe.checkMachO(); + const check_exe = exe.checkObject(.macho); check_exe.check("segname __TEXT"); check_exe.checkNextExtract("vmaddr {vmaddr}"); diff --git a/test/link/macho/pagezero/build.zig b/test/link/macho/pagezero/build.zig index 05bc4052fe..a35be919c5 100644 --- a/test/link/macho/pagezero/build.zig +++ b/test/link/macho/pagezero/build.zig @@ -14,7 +14,7 @@ pub fn build(b: *Builder) void { exe.linkLibC(); exe.pagezero_size = 0x4000; - const check = exe.checkMachO(); + const check = exe.checkObject(.macho); check.check("LC 0"); check.checkNext("segname __PAGEZERO"); check.checkNext("vmaddr 0"); @@ -33,7 +33,7 @@ pub fn build(b: *Builder) void { exe.linkLibC(); exe.pagezero_size = 0; - const check = exe.checkMachO(); + const check = exe.checkObject(.macho); check.check("LC 0"); check.checkNext("segname __TEXT"); check.checkNext("vmaddr 0"); diff --git a/test/link/macho/stack_size/build.zig b/test/link/macho/stack_size/build.zig index 10b5a3f83d..b840f8928c 100644 --- a/test/link/macho/stack_size/build.zig +++ b/test/link/macho/stack_size/build.zig @@ -13,7 +13,7 @@ pub fn build(b: *Builder) void { exe.linkLibC(); exe.stack_size = 0x100000000; - const check_exe = exe.checkMachO(); + const check_exe = exe.checkObject(.macho); check_exe.check("cmd MAIN"); check_exe.checkNext("stacksize 100000000");