Commit a2d8e03931

Jakub Konka <kubkon@jakubkonka.com>
2023-12-10 00:50:52
lib/std/Build/CheckObject: dump Mach-O dyld_info_only bind, weak-bind and lazy-bind data
1 parent 5bda88f
Changed files (1)
lib
std
Build
lib/std/Build/Step/CheckObject.zig
@@ -577,9 +577,12 @@ const MachODumper = struct {
         const writer = output.writer();
 
         var symtab: ?Symtab = null;
-        var segments = std.ArrayList(macho.segment_command_u64);
+        var segments = std.ArrayList(macho.segment_command_64).init(gpa);
+        defer segments.deinit();
         var sections = std.ArrayList(macho.section_64).init(gpa);
+        defer sections.deinit();
         var imports = std.ArrayList([]const u8).init(gpa);
+        defer imports.deinit();
         var text_seg: ?u8 = null;
         var dyld_info_lc: ?macho.dyld_info_command = null;
 
@@ -636,10 +639,27 @@ const MachODumper = struct {
             try writer.writeAll(dyld_info_label ++ "\n");
             if (lc.rebase_size > 0) {
                 const data = bytes[lc.rebase_off..][0..lc.rebase_size];
+                try writer.writeAll("rebase info\n");
                 try dumpRebaseInfo(gpa, data, segments.items, writer);
             }
+            if (lc.bind_size > 0) {
+                const data = bytes[lc.bind_off..][0..lc.bind_size];
+                try writer.writeAll("bind info\n");
+                try dumpBindInfo(gpa, data, segments.items, imports.items, false, writer);
+            }
+            if (lc.weak_bind_size > 0) {
+                const data = bytes[lc.weak_bind_off..][0..lc.weak_bind_size];
+                try writer.writeAll("weak bind info\n");
+                try dumpBindInfo(gpa, data, segments.items, imports.items, false, writer);
+            }
+            if (lc.lazy_bind_size > 0) {
+                const data = bytes[lc.lazy_bind_off..][0..lc.lazy_bind_size];
+                try writer.writeAll("lazy bind info\n");
+                try dumpBindInfo(gpa, data, segments.items, imports.items, true, writer);
+            }
             if (lc.export_size > 0) {
                 const data = bytes[lc.export_off..][0..lc.export_size];
+                try writer.writeAll("exports\n");
                 try dumpExportsTrie(gpa, data, segments.items[text_seg.?], writer);
             }
         }
@@ -1011,7 +1031,6 @@ const MachODumper = struct {
         defer rebases.deinit();
         try parseRebaseInfo(data, segments, &rebases);
         mem.sort(u64, rebases.items, {}, std.sort.asc(u64));
-        try writer.writeAll("rebase info\n");
         for (rebases.items) |addr| {
             try writer.print("0x{x}\n", .{addr});
         }
@@ -1048,7 +1067,7 @@ const MachODumper = struct {
                 },
                 macho.REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB => {
                     const addend = try std.leb.readULEB128(u64, reader);
-                    const seg = segments.items[seg_id.?];
+                    const seg = segments[seg_id.?];
                     const addr = seg.vmaddr + offset;
                     try rebases.append(addr);
                     offset += addend + @sizeOf(u64);
@@ -1072,7 +1091,7 @@ const MachODumper = struct {
                         },
                         else => unreachable,
                     }
-                    const seg = segments.items[seg_id.?];
+                    const seg = segments[seg_id.?];
                     const base_addr = seg.vmaddr;
                     var count: usize = 0;
                     while (count < ntimes) : (count += 1) {
@@ -1086,6 +1105,143 @@ const MachODumper = struct {
         }
     }
 
+    const Binding = struct {
+        address: u64,
+        addend: i64,
+        ordinal: ?u16,
+        name: []const u8,
+
+        fn deinit(binding: *Binding, gpa: Allocator) void {
+            gpa.free(binding.name);
+        }
+
+        fn lessThan(ctx: void, lhs: Binding, rhs: Binding) bool {
+            _ = ctx;
+            return lhs.address < rhs.address;
+        }
+    };
+
+    fn dumpBindInfo(
+        gpa: Allocator,
+        data: []const u8,
+        segments: []const macho.segment_command_64,
+        dylibs: []const []const u8,
+        is_lazy: bool,
+        writer: anytype,
+    ) !void {
+        var bindings = std.ArrayList(Binding).init(gpa);
+        defer {
+            for (bindings.items) |*b| {
+                b.deinit(gpa);
+            }
+            bindings.deinit();
+        }
+        try parseBindInfo(gpa, data, segments, &bindings, is_lazy);
+        mem.sort(Binding, bindings.items, {}, Binding.lessThan);
+        for (bindings.items) |binding| {
+            try writer.print("0x{x} [addend: {d}]", .{ binding.address, binding.addend });
+            if (binding.ordinal) |ord| {
+                try writer.print(" ({s})", .{std.fs.path.basename(dylibs[ord - 1])});
+            }
+            try writer.print(" {s}\n", .{binding.name});
+        }
+    }
+
+    fn parseBindInfo(
+        gpa: Allocator,
+        data: []const u8,
+        segments: []const macho.segment_command_64,
+        bindings: *std.ArrayList(Binding),
+        lazy_ops: bool,
+    ) !void {
+        var stream = std.io.fixedBufferStream(data);
+        var creader = std.io.countingReader(stream.reader());
+        const reader = creader.reader();
+
+        var seg_id: ?u8 = null;
+        var dylib_id: ?u16 = null;
+        var offset: u64 = 0;
+        var addend: i64 = 0;
+
+        var name_buf = std.ArrayList(u8).init(gpa);
+        defer name_buf.deinit();
+
+        while (true) {
+            const byte = reader.readByte() catch break;
+            const opc = byte & macho.BIND_OPCODE_MASK;
+            const imm = byte & macho.BIND_IMMEDIATE_MASK;
+            switch (opc) {
+                macho.BIND_OPCODE_DONE => {
+                    if (!lazy_ops) break;
+                },
+                macho.BIND_OPCODE_SET_TYPE_IMM => {
+                    if (lazy_ops) break;
+                },
+                macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM => {
+                    dylib_id = imm;
+                },
+                macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB => {
+                    seg_id = imm;
+                    offset = try std.leb.readULEB128(u64, reader);
+                },
+                macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM => {
+                    name_buf.clearRetainingCapacity();
+                    try reader.readUntilDelimiterArrayList(&name_buf, 0, std.math.maxInt(u32));
+                    try name_buf.append(0);
+                },
+                macho.BIND_OPCODE_SET_ADDEND_SLEB => {
+                    addend = try std.leb.readILEB128(i64, reader);
+                },
+                macho.BIND_OPCODE_ADD_ADDR_ULEB => {
+                    if (lazy_ops) break;
+                    const x = try std.leb.readULEB128(u64, reader);
+                    offset = @intCast(@as(i64, @intCast(offset)) + @as(i64, @bitCast(x)));
+                },
+                macho.BIND_OPCODE_DO_BIND,
+                macho.BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB,
+                macho.BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED,
+                macho.BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB,
+                => {
+                    var add_addr: u64 = 0;
+                    var count: u64 = 1;
+                    var skip: u64 = 0;
+
+                    switch (opc) {
+                        macho.BIND_OPCODE_DO_BIND => {},
+                        macho.BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB => {
+                            if (lazy_ops) break;
+                            add_addr = try std.leb.readULEB128(u64, reader);
+                        },
+                        macho.BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED => {
+                            if (lazy_ops) break;
+                            add_addr = imm * @sizeOf(u64);
+                        },
+                        macho.BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB => {
+                            if (lazy_ops) break;
+                            count = try std.leb.readULEB128(u64, reader);
+                            skip = try std.leb.readULEB128(u64, reader);
+                        },
+                        else => unreachable,
+                    }
+
+                    const seg = segments[seg_id.?];
+                    var i: u64 = 0;
+                    while (i < count) : (i += 1) {
+                        const addr: u64 = @intCast(@as(i64, @intCast(seg.vmaddr + offset)));
+                        try bindings.append(.{
+                            .address = addr,
+                            .addend = addend,
+                            .ordinal = dylib_id,
+                            .name = try gpa.dupe(u8, name_buf.items),
+                        });
+                        offset += skip + @sizeOf(u64) + add_addr;
+                    }
+                },
+                else => break,
+            }
+        }
+    }
+
     fn dumpExportsTrie(
         gpa: Allocator,
         data: []const u8,
@@ -1101,7 +1257,6 @@ const MachODumper = struct {
 
         mem.sort(Export, exports.items, {}, Export.lessThan);
 
-        try writer.writeAll("exports\n");
         for (exports.items) |exp| {
             switch (exp.tag) {
                 .@"export" => {