Commit 1e5075f812

Jakub Konka <kubkon@jakubkonka.com>
2024-04-15 12:36:32
lib/std/Build/Step/CheckObject: dump section as string
1 parent d483ba7
Changed files (1)
lib
std
Build
lib/std/Build/Step/CheckObject.zig
@@ -247,15 +247,27 @@ const ComputeCompareExpected = struct {
 
 const Check = struct {
     kind: Kind,
+    payload: Payload,
+    data: std.ArrayList(u8),
     actions: std.ArrayList(Action),
 
     fn create(allocator: Allocator, kind: Kind) Check {
         return .{
             .kind = kind,
+            .payload = .{ .none = {} },
+            .data = std.ArrayList(u8).init(allocator),
             .actions = std.ArrayList(Action).init(allocator),
         };
     }
 
+    fn dumpSection(allocator: Allocator, name: [:0]const u8) Check {
+        var check = Check.create(allocator, .dump_section);
+        const off: u32 = @intCast(check.data.items.len);
+        check.data.writer().print("{s}\x00", .{name}) catch @panic("OOM");
+        check.payload = .{ .dump_section = off };
+        return check;
+    }
+
     fn extract(self: *Check, phrase: SearchPhrase) void {
         self.actions.append(.{
             .tag = .extract,
@@ -305,6 +317,13 @@ const Check = struct {
         dyld_lazy_bind,
         exports,
         compute_compare,
+        dump_section,
+    };
+
+    const Payload = union {
+        none: void,
+        /// Null-delimited string in the 'data' buffer.
+        dump_section: u32,
     };
 };
 
@@ -513,6 +532,11 @@ pub fn checkInArchiveSymtab(self: *CheckObject) void {
     self.checkExact(label);
 }
 
+pub fn dumpSection(self: *CheckObject, name: [:0]const u8) void {
+    const new_check = Check.dumpSection(self.step.owner.allocator, name);
+    self.checks.append(new_check) catch @panic("OOM");
+}
+
 /// Creates a new standalone, singular check which allows running simple binary operations
 /// on the extracted variables. It will then compare the reduced program with the value of
 /// the expected variable.
@@ -564,13 +588,44 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
         }
 
         const output = switch (self.obj_format) {
-            .macho => try MachODumper.parseAndDump(step, chk.kind, contents),
-            .elf => try ElfDumper.parseAndDump(step, chk.kind, contents),
+            .macho => try MachODumper.parseAndDump(step, chk, contents),
+            .elf => try ElfDumper.parseAndDump(step, chk, contents),
             .coff => return step.fail("TODO coff parser", .{}),
-            .wasm => try WasmDumper.parseAndDump(step, chk.kind, contents),
+            .wasm => try WasmDumper.parseAndDump(step, chk, contents),
             else => unreachable,
         };
 
+        // Depending on whether we requested dumping section verbatim or not,
+        // we either format message string with escaped codes, or not to aid debugging
+        // the failed test.
+        const fmtMessageString = struct {
+            fn fmtMessageString(kind: Check.Kind, msg: []const u8) std.fmt.Formatter(formatMessageString) {
+                return .{ .data = .{
+                    .kind = kind,
+                    .msg = msg,
+                } };
+            }
+
+            const Ctx = struct {
+                kind: Check.Kind,
+                msg: []const u8,
+            };
+
+            fn formatMessageString(
+                ctx: Ctx,
+                comptime unused_fmt_string: []const u8,
+                options: std.fmt.FormatOptions,
+                writer: anytype,
+            ) !void {
+                _ = unused_fmt_string;
+                _ = options;
+                switch (ctx.kind) {
+                    .dump_section => try writer.print("{s}", .{std.fmt.fmtSliceEscapeLower(ctx.msg)}),
+                    else => try writer.writeAll(ctx.msg),
+                }
+            }
+        }.fmtMessageString;
+
         var it = mem.tokenizeAny(u8, output, "\r\n");
         for (chk.actions.items) |act| {
             switch (act.tag) {
@@ -585,7 +640,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
                             \\========= but parsed file does not contain it: =======
                             \\{s}
                             \\======================================================
-                        , .{ act.phrase.resolve(b, step), output });
+                        , .{ fmtMessageString(chk.kind, act.phrase.resolve(b, step)), fmtMessageString(chk.kind, output) });
                     }
                 },
 
@@ -600,7 +655,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
                             \\========= but parsed file does not contain it: =======
                             \\{s}
                             \\======================================================
-                        , .{ act.phrase.resolve(b, step), output });
+                        , .{ fmtMessageString(chk.kind, act.phrase.resolve(b, step)), fmtMessageString(chk.kind, output) });
                     }
                 },
 
@@ -614,7 +669,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
                             \\========= but parsed file does contain it: ========
                             \\{s}
                             \\===================================================
-                        , .{ act.phrase.resolve(b, step), output });
+                        , .{ fmtMessageString(chk.kind, act.phrase.resolve(b, step)), fmtMessageString(chk.kind, output) });
                     }
                 },
 
@@ -629,7 +684,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
                             \\========= but parsed file does not contain it: =======
                             \\{s}
                             \\======================================================
-                        , .{ act.phrase.resolve(b, step), output });
+                        , .{ act.phrase.resolve(b, step), fmtMessageString(chk.kind, output) });
                     }
                 },
 
@@ -660,7 +715,7 @@ const MachODumper = struct {
         }
     };
 
-    fn parseAndDump(step: *Step, kind: Check.Kind, bytes: []const u8) ![]const u8 {
+    fn parseAndDump(step: *Step, check: Check, bytes: []const u8) ![]const u8 {
         const gpa = step.owner.allocator;
         var stream = std.io.fixedBufferStream(bytes);
         const reader = stream.reader();
@@ -731,7 +786,7 @@ const MachODumper = struct {
             }
         }
 
-        switch (kind) {
+        switch (check.kind) {
             .headers => {
                 try dumpHeader(hdr, writer);
 
@@ -764,7 +819,7 @@ const MachODumper = struct {
                 if (dyld_info_lc == null) return step.fail("no dyld info found", .{});
                 const lc = dyld_info_lc.?;
 
-                switch (kind) {
+                switch (check.kind) {
                     .dyld_rebase => if (lc.rebase_size > 0) {
                         const data = bytes[lc.rebase_off..][0..lc.rebase_size];
                         try writer.writeAll(dyld_rebase_label ++ "\n");
@@ -805,7 +860,7 @@ const MachODumper = struct {
                 return step.fail("no exports data found", .{});
             },
 
-            else => return step.fail("invalid check kind for MachO file format: {s}", .{@tagName(kind)}),
+            else => return step.fail("invalid check kind for MachO file format: {s}", .{@tagName(check.kind)}),
         }
 
         return output.toOwnedSlice();
@@ -1633,14 +1688,14 @@ const ElfDumper = struct {
     const dynamic_section_label = "dynamic section";
     const archive_symtab_label = "archive symbol table";
 
-    fn parseAndDump(step: *Step, kind: Check.Kind, bytes: []const u8) ![]const u8 {
-        return parseAndDumpArchive(step, kind, bytes) catch |err| switch (err) {
-            error.InvalidArchiveMagicNumber => try parseAndDumpObject(step, kind, bytes),
+    fn parseAndDump(step: *Step, check: Check, bytes: []const u8) ![]const u8 {
+        return parseAndDumpArchive(step, check, bytes) catch |err| switch (err) {
+            error.InvalidArchiveMagicNumber => try parseAndDumpObject(step, check, bytes),
             else => |e| return e,
         };
     }
 
-    fn parseAndDumpArchive(step: *Step, kind: Check.Kind, bytes: []const u8) ![]const u8 {
+    fn parseAndDumpArchive(step: *Step, check: Check, bytes: []const u8) ![]const u8 {
         const gpa = step.owner.allocator;
         var stream = std.io.fixedBufferStream(bytes);
         const reader = stream.reader();
@@ -1702,13 +1757,13 @@ const ElfDumper = struct {
         var output = std.ArrayList(u8).init(gpa);
         const writer = output.writer();
 
-        switch (kind) {
+        switch (check.kind) {
             .archive_symtab => if (ctx.symtab.items.len > 0) {
                 try ctx.dumpSymtab(writer);
             } else return step.fail("no archive symbol table found", .{}),
 
             else => if (ctx.objects.items.len > 0) {
-                try ctx.dumpObjects(step, kind, writer);
+                try ctx.dumpObjects(step, check, writer);
             } else return step.fail("empty archive", .{}),
         }
 
@@ -1785,10 +1840,10 @@ const ElfDumper = struct {
             }
         }
 
-        fn dumpObjects(ctx: ArchiveContext, step: *Step, kind: Check.Kind, writer: anytype) !void {
+        fn dumpObjects(ctx: ArchiveContext, step: *Step, check: Check, writer: anytype) !void {
             for (ctx.objects.items) |object| {
                 try writer.print("object {s}\n", .{object.name});
-                const output = try parseAndDumpObject(step, kind, ctx.data[object.off..][0..object.len]);
+                const output = try parseAndDumpObject(step, check, ctx.data[object.off..][0..object.len]);
                 defer ctx.gpa.free(output);
                 try writer.print("{s}\n", .{output});
             }
@@ -1806,7 +1861,7 @@ const ElfDumper = struct {
         };
     };
 
-    fn parseAndDumpObject(step: *Step, kind: Check.Kind, bytes: []const u8) ![]const u8 {
+    fn parseAndDumpObject(step: *Step, check: Check, bytes: []const u8) ![]const u8 {
         const gpa = step.owner.allocator;
         var stream = std.io.fixedBufferStream(bytes);
         const reader = stream.reader();
@@ -1859,7 +1914,7 @@ const ElfDumper = struct {
         var output = std.ArrayList(u8).init(gpa);
         const writer = output.writer();
 
-        switch (kind) {
+        switch (check.kind) {
             .headers => {
                 try ctx.dumpHeader(writer);
                 try ctx.dumpShdrs(writer);
@@ -1878,7 +1933,13 @@ const ElfDumper = struct {
                 try ctx.dumpDynamicSection(shndx, writer);
             } else return step.fail("no .dynamic section found", .{}),
 
-            else => return step.fail("invalid check kind for ELF file format: {s}", .{@tagName(kind)}),
+            .dump_section => {
+                const name = mem.sliceTo(@as([*:0]const u8, @ptrCast(check.data.items.ptr + check.payload.dump_section)), 0);
+                const shndx = ctx.getSectionByName(name) orelse return step.fail("no '{s}' section found", .{name});
+                try ctx.dumpSection(shndx, writer);
+            },
+
+            else => return step.fail("invalid check kind for ELF file format: {s}", .{@tagName(check.kind)}),
         }
 
         return output.toOwnedSlice();
@@ -2176,6 +2237,11 @@ const ElfDumper = struct {
             }
         }
 
+        fn dumpSection(ctx: ObjectContext, shndx: usize, writer: anytype) !void {
+            const data = ctx.getSectionContents(shndx);
+            try writer.print("{s}", .{data});
+        }
+
         inline fn getSectionName(ctx: ObjectContext, shndx: usize) []const u8 {
             const shdr = ctx.shdrs[shndx];
             return getString(ctx.shstrtab, shdr.sh_name);
@@ -2300,7 +2366,7 @@ const ElfDumper = struct {
 const WasmDumper = struct {
     const symtab_label = "symbols";
 
-    fn parseAndDump(step: *Step, kind: Check.Kind, bytes: []const u8) ![]const u8 {
+    fn parseAndDump(step: *Step, check: Check, bytes: []const u8) ![]const u8 {
         const gpa = step.owner.allocator;
         var fbs = std.io.fixedBufferStream(bytes);
         const reader = fbs.reader();
@@ -2317,7 +2383,7 @@ const WasmDumper = struct {
         errdefer output.deinit();
         const writer = output.writer();
 
-        switch (kind) {
+        switch (check.kind) {
             .headers => {
                 while (reader.readByte()) |current_byte| {
                     const section = std.meta.intToEnum(std.wasm.Section, current_byte) catch {
@@ -2330,7 +2396,7 @@ const WasmDumper = struct {
                 } else |_| {} // reached end of stream
             },
 
-            else => return step.fail("invalid check kind for Wasm file format: {s}", .{@tagName(kind)}),
+            else => return step.fail("invalid check kind for Wasm file format: {s}", .{@tagName(check.kind)}),
         }
 
         return output.toOwnedSlice();
@@ -2364,7 +2430,7 @@ const WasmDumper = struct {
             => {
                 const entries = try std.leb.readULEB128(u32, reader);
                 try writer.print("\nentries {d}\n", .{entries});
-                try dumpSection(step, section, data[fbs.pos..], entries, writer);
+                try parseSection(step, section, data[fbs.pos..], entries, writer);
             },
             .custom => {
                 const name_length = try std.leb.readULEB128(u32, reader);
@@ -2393,7 +2459,7 @@ const WasmDumper = struct {
         }
     }
 
-    fn dumpSection(step: *Step, section: std.wasm.Section, data: []const u8, entries: u32, writer: anytype) !void {
+    fn parseSection(step: *Step, section: std.wasm.Section, data: []const u8, entries: u32, writer: anytype) !void {
         var fbs = std.io.fixedBufferStream(data);
         const reader = fbs.reader();