Commit ee6e25bc13

Jakub Konka <kubkon@jakubkonka.com>
2021-07-02 00:13:46
zld: add Symbol.Stab and move nlist creation logic there
1 parent 2b3bda4
Changed files (5)
src/link/MachO/Archive.zig
@@ -81,6 +81,11 @@ const ar_hdr = extern struct {
         }
     }
 
+    fn date(self: ar_hdr) !u64 {
+        const value = getValue(&self.ar_date);
+        return std.fmt.parseInt(u64, value, 10);
+    }
+
     fn size(self: ar_hdr) !u32 {
         const value = getValue(&self.ar_size);
         return std.fmt.parseInt(u32, value, 10);
@@ -264,6 +269,7 @@ pub fn parseObject(self: Archive, offset: u32) !*Object {
         .file = try fs.cwd().openFile(self.name.?, .{}),
         .name = name,
         .file_offset = @intCast(u32, try reader.context.getPos()),
+        .mtime = try self.header.?.date(),
     };
     try object.parse();
     try reader.context.seekTo(0);
src/link/MachO/Dylib.zig
@@ -18,7 +18,6 @@ const LibStub = @import("../tapi.zig").LibStub;
 usingnamespace @import("commands.zig");
 
 allocator: *Allocator,
-
 arch: ?Arch = null,
 header: ?macho.mach_header_64 = null,
 file: ?fs.File = null,
src/link/MachO/Object.zig
@@ -24,6 +24,7 @@ header: ?macho.mach_header_64 = null,
 file: ?fs.File = null,
 file_offset: ?u32 = null,
 name: ?[]const u8 = null,
+mtime: ?u64 = null,
 
 load_commands: std.ArrayListUnmanaged(LoadCommand) = .{},
 sections: std.ArrayListUnmanaged(Section) = .{},
@@ -45,12 +46,10 @@ dwarf_debug_line_index: ?u16 = null,
 dwarf_debug_ranges_index: ?u16 = null,
 
 symbols: std.ArrayListUnmanaged(*Symbol) = .{},
+stabs: std.ArrayListUnmanaged(*Symbol) = .{},
 initializers: std.ArrayListUnmanaged(*Symbol) = .{},
 data_in_code_entries: std.ArrayListUnmanaged(macho.data_in_code_entry) = .{},
 
-tu_path: ?[]const u8 = null,
-tu_mtime: ?u64 = null,
-
 pub const Section = struct {
     inner: macho.section_64,
     code: []u8,
@@ -223,16 +222,18 @@ pub fn deinit(self: *Object) void {
     }
     self.symbols.deinit(self.allocator);
 
+    for (self.stabs.items) |stab| {
+        stab.deinit(self.allocator);
+        self.allocator.destroy(stab);
+    }
+    self.stabs.deinit(self.allocator);
+
     self.data_in_code_entries.deinit(self.allocator);
     self.initializers.deinit(self.allocator);
 
     if (self.name) |n| {
         self.allocator.free(n);
     }
-
-    if (self.tu_path) |tu_path| {
-        self.allocator.free(tu_path);
-    }
 }
 
 pub fn closeFile(self: Object) void {
@@ -484,11 +485,33 @@ pub fn parseDebugInfo(self: *Object) !void {
     const name = try compile_unit.die.getAttrString(&debug_info.inner, dwarf.AT_name);
     const comp_dir = try compile_unit.die.getAttrString(&debug_info.inner, dwarf.AT_comp_dir);
 
-    self.tu_path = try std.fs.path.join(self.allocator, &[_][]const u8{ comp_dir, name });
-    self.tu_mtime = mtime: {
-        const stat = try self.file.?.stat();
-        break :mtime @intCast(u64, @divFloor(stat.mtime, 1_000_000_000));
-    };
+    if (self.mtime == null) {
+        self.mtime = mtime: {
+            const file = self.file orelse break :mtime 0;
+            const stat = file.stat() catch break :mtime 0;
+            break :mtime @intCast(u64, @divFloor(stat.mtime, 1_000_000_000));
+        };
+    }
+
+    try self.stabs.ensureUnusedCapacity(self.allocator, self.symbols.items.len + 4);
+
+    // Current dir
+    self.stabs.appendAssumeCapacity(try Symbol.Stab.new(self.allocator, comp_dir, .{
+        .kind = .so,
+        .file = self,
+    }));
+
+    // Artifact name
+    self.stabs.appendAssumeCapacity(try Symbol.Stab.new(self.allocator, name, .{
+        .kind = .so,
+        .file = self,
+    }));
+
+    // Path to object file with debug info
+    self.stabs.appendAssumeCapacity(try Symbol.Stab.new(self.allocator, self.name.?, .{
+        .kind = .oso,
+        .file = self,
+    }));
 
     for (self.symbols.items) |sym| {
         if (sym.cast(Symbol.Regular)) |reg| {
@@ -500,7 +523,7 @@ pub fn parseDebugInfo(self: *Object) !void {
                 }
             } else 0;
 
-            reg.stab = .{
+            const stab = try Symbol.Stab.new(self.allocator, sym.name, .{
                 .kind = kind: {
                     if (size > 0) break :kind .function;
                     switch (reg.linkage) {
@@ -509,9 +532,27 @@ pub fn parseDebugInfo(self: *Object) !void {
                     }
                 },
                 .size = size,
-            };
+                .symbol = sym,
+                .file = self,
+            });
+            self.stabs.appendAssumeCapacity(stab);
+        } else if (sym.cast(Symbol.Tentative)) |_| {
+            const stab = try Symbol.Stab.new(self.allocator, sym.name, .{
+                .kind = .global,
+                .size = 0,
+                .symbol = sym,
+                .file = self,
+            });
+            self.stabs.appendAssumeCapacity(stab);
         }
     }
+
+    // Closing delimiter.
+    const delim_stab = try Symbol.Stab.new(self.allocator, "", .{
+        .kind = .so,
+        .file = self,
+    });
+    self.stabs.appendAssumeCapacity(delim_stab);
 }
 
 fn readSection(self: Object, allocator: *Allocator, index: u16) ![]u8 {
src/link/MachO/Symbol.zig
@@ -10,6 +10,7 @@ const Object = @import("Object.zig");
 const StringTable = @import("StringTable.zig");
 
 pub const Type = enum {
+    stab,
     regular,
     proxy,
     unresolved,
@@ -31,6 +32,151 @@ got_index: ?u32 = null,
 /// Index in stubs table for late binding.
 stubs_index: ?u32 = null,
 
+pub const Stab = struct {
+    base: Symbol,
+
+    // Symbol kind: function, etc.
+    kind: Kind,
+
+    // Size of stab.
+    size: u64,
+
+    // Base regular symbol for this stub if defined.
+    symbol: ?*Symbol = null,
+
+    // null means self-reference.
+    file: ?*Object = null,
+
+    pub const base_type: Symbol.Type = .stab;
+
+    pub const Kind = enum {
+        so,
+        oso,
+        function,
+        global,
+        static,
+    };
+
+    const Opts = struct {
+        kind: Kind = .so,
+        size: u64 = 0,
+        symbol: ?*Symbol = null,
+        file: ?*Object = null,
+    };
+
+    pub fn new(allocator: *Allocator, name: []const u8, opts: Opts) !*Symbol {
+        const stab = try allocator.create(Stab);
+        errdefer allocator.destroy(stab);
+
+        stab.* = .{
+            .base = .{
+                .@"type" = .stab,
+                .name = try allocator.dupe(u8, name),
+            },
+            .kind = opts.kind,
+            .size = opts.size,
+            .symbol = opts.symbol,
+            .file = opts.file,
+        };
+
+        return &stab.base;
+    }
+
+    pub fn asNlists(stab: *Stab, allocator: *Allocator, strtab: *StringTable) ![]macho.nlist_64 {
+        var out = std.ArrayList(macho.nlist_64).init(allocator);
+        defer out.deinit();
+        if (stab.kind == .so) {
+            try out.append(.{
+                .n_strx = try strtab.getOrPut(stab.base.name),
+                .n_type = macho.N_SO,
+                .n_sect = 0,
+                .n_desc = 0,
+                .n_value = 0,
+            });
+        } else if (stab.kind == .oso) {
+            const mtime = mtime: {
+                const object = stab.file orelse break :mtime 0;
+                break :mtime object.mtime orelse 0;
+            };
+            try out.append(.{
+                .n_strx = try strtab.getOrPut(stab.base.name),
+                .n_type = macho.N_OSO,
+                .n_sect = 0,
+                .n_desc = 1,
+                .n_value = mtime,
+            });
+        } else outer: {
+            const symbol = stab.symbol orelse unreachable;
+            const regular = symbol.getTopmostAlias().cast(Regular) orelse unreachable;
+            const is_match = blk: {
+                if (regular.file == null and stab.file == null) break :blk true;
+                if (regular.file) |f1| {
+                    if (stab.file) |f2| {
+                        if (f1 == f2) break :blk true;
+                    }
+                }
+                break :blk false;
+            };
+            if (!is_match) break :outer;
+
+            switch (stab.kind) {
+                .function => {
+                    try out.ensureUnusedCapacity(4);
+                    out.appendAssumeCapacity(.{
+                        .n_strx = 0,
+                        .n_type = macho.N_BNSYM,
+                        .n_sect = regular.section,
+                        .n_desc = 0,
+                        .n_value = regular.address,
+                    });
+                    out.appendAssumeCapacity(.{
+                        .n_strx = try strtab.getOrPut(stab.base.name),
+                        .n_type = macho.N_FUN,
+                        .n_sect = regular.section,
+                        .n_desc = 0,
+                        .n_value = regular.address,
+                    });
+                    out.appendAssumeCapacity(.{
+                        .n_strx = 0,
+                        .n_type = macho.N_FUN,
+                        .n_sect = 0,
+                        .n_desc = 0,
+                        .n_value = stab.size,
+                    });
+                    out.appendAssumeCapacity(.{
+                        .n_strx = 0,
+                        .n_type = macho.N_ENSYM,
+                        .n_sect = regular.section,
+                        .n_desc = 0,
+                        .n_value = stab.size,
+                    });
+                },
+                .global => {
+                    try out.append(.{
+                        .n_strx = try strtab.getOrPut(stab.base.name),
+                        .n_type = macho.N_GSYM,
+                        .n_sect = 0,
+                        .n_desc = 0,
+                        .n_value = 0,
+                    });
+                },
+                .static => {
+                    try out.append(.{
+                        .n_strx = try strtab.getOrPut(stab.base.name),
+                        .n_type = macho.N_STSYM,
+                        .n_sect = regular.section,
+                        .n_desc = 0,
+                        .n_value = regular.address,
+                    });
+                },
+                .so, .oso => unreachable,
+            }
+        }
+
+        return out.toOwnedSlice();
+    }
+};
+
 pub const Regular = struct {
     base: Symbol,
 
@@ -50,9 +196,6 @@ pub const Regular = struct {
     /// null means self-reference.
     file: ?*Object = null,
 
-    /// Debug stab if defined.
-    stab: ?Stab = null,
-
     /// True if symbol was already committed into the final
     /// symbol table.
     visited: bool = false,
@@ -65,25 +208,12 @@ pub const Regular = struct {
         global,
     };
 
-    pub const Stab = struct {
-        /// Stab kind
-        kind: enum {
-            function,
-            global,
-            static,
-        },
-
-        /// Size of the stab.
-        size: u64,
-    };
-
     const Opts = struct {
         linkage: Linkage = .translation_unit,
         address: u64 = 0,
         section: u8 = 0,
         weak_ref: bool = false,
         file: ?*Object = null,
-        stab: ?Stab = null,
     };
 
     pub fn new(allocator: *Allocator, name: []const u8, opts: Opts) !*Symbol {
@@ -100,7 +230,6 @@ pub const Regular = struct {
             .section = opts.section,
             .weak_ref = opts.weak_ref,
             .file = opts.file,
-            .stab = opts.stab,
         };
 
         return &reg.base;
@@ -304,15 +433,6 @@ pub fn getTopmostAlias(base: *Symbol) *Symbol {
     return base;
 }
 
-pub fn asNlist(base: *Symbol, strtab: *StringTable) !macho.nlist_64 {
-    return switch (base.tag) {
-        .regular => @fieldParentPtr(Regular, "base", base).asNlist(strtab),
-        .proxy => @fieldParentPtr(Proxy, "base", base).asNlist(strtab),
-        .unresolved => @fieldParentPtr(Unresolved, "base", base).asNlist(strtab),
-        .tentative => @fieldParentPtr(Tentative, "base", base).asNlist(strtab),
-    };
-}
-
 pub fn isStab(sym: macho.nlist_64) bool {
     return (macho.N_STAB & sym.n_type) != 0;
 }
src/link/MachO/Zld.zig
@@ -1137,10 +1137,6 @@ fn allocateTentativeSymbols(self: *Zld) !void {
             .section = section,
             .weak_ref = false,
             .file = tent.file,
-            .stab = .{
-                .kind = .global,
-                .size = 0,
-            },
         });
         reg.got_index = tent.base.got_index;
         reg.stubs_index = tent.base.stubs_index;
@@ -2338,7 +2334,6 @@ fn flush(self: *Zld) !void {
         symtab.symoff = @intCast(u32, seg.inner.fileoff + seg.inner.filesize);
     }
 
-    try self.writeDebugInfo();
     try self.writeSymbolTable();
     try self.writeStringTable();
 
@@ -2711,138 +2706,6 @@ fn writeExportInfo(self: *Zld) !void {
     try self.file.?.pwriteAll(buffer, dyld_info.export_off);
 }
 
-fn writeDebugInfo(self: *Zld) !void {
-    var stabs = std.ArrayList(macho.nlist_64).init(self.allocator);
-    defer stabs.deinit();
-
-    for (self.objects.items) |object| {
-        const tu_path = object.tu_path orelse continue;
-        const tu_mtime = object.tu_mtime orelse continue;
-        _ = tu_mtime;
-        const dirname = std.fs.path.dirname(tu_path) orelse "./";
-        // Current dir
-        try stabs.append(.{
-            .n_strx = try self.strtab.getOrPut(tu_path[0 .. dirname.len + 1]),
-            .n_type = macho.N_SO,
-            .n_sect = 0,
-            .n_desc = 0,
-            .n_value = 0,
-        });
-        // Artifact name
-        try stabs.append(.{
-            .n_strx = try self.strtab.getOrPut(tu_path[dirname.len + 1 ..]),
-            .n_type = macho.N_SO,
-            .n_sect = 0,
-            .n_desc = 0,
-            .n_value = 0,
-        });
-        // Path to object file with debug info
-        try stabs.append(.{
-            .n_strx = try self.strtab.getOrPut(object.name.?),
-            .n_type = macho.N_OSO,
-            .n_sect = 0,
-            .n_desc = 1,
-            .n_value = 0, //tu_mtime, TODO figure out why precalculated mtime value doesn't work
-        });
-
-        for (object.symbols.items) |sym| {
-            const reg = reg: {
-                switch (sym.@"type") {
-                    .regular => break :reg sym.cast(Symbol.Regular) orelse unreachable,
-                    .tentative => {
-                        const final = sym.getTopmostAlias().cast(Symbol.Regular) orelse unreachable;
-                        if (object != final.file) continue;
-                        break :reg final;
-                    },
-                    else => continue,
-                }
-            };
-
-            if (reg.isTemp() or reg.stab == null) continue;
-            const stab = reg.stab orelse unreachable;
-
-            switch (stab.kind) {
-                .function => {
-                    try stabs.append(.{
-                        .n_strx = 0,
-                        .n_type = macho.N_BNSYM,
-                        .n_sect = reg.section,
-                        .n_desc = 0,
-                        .n_value = reg.address,
-                    });
-                    try stabs.append(.{
-                        .n_strx = try self.strtab.getOrPut(sym.name),
-                        .n_type = macho.N_FUN,
-                        .n_sect = reg.section,
-                        .n_desc = 0,
-                        .n_value = reg.address,
-                    });
-                    try stabs.append(.{
-                        .n_strx = 0,
-                        .n_type = macho.N_FUN,
-                        .n_sect = 0,
-                        .n_desc = 0,
-                        .n_value = stab.size,
-                    });
-                    try stabs.append(.{
-                        .n_strx = 0,
-                        .n_type = macho.N_ENSYM,
-                        .n_sect = reg.section,
-                        .n_desc = 0,
-                        .n_value = stab.size,
-                    });
-                },
-                .global => {
-                    try stabs.append(.{
-                        .n_strx = try self.strtab.getOrPut(sym.name),
-                        .n_type = macho.N_GSYM,
-                        .n_sect = 0,
-                        .n_desc = 0,
-                        .n_value = 0,
-                    });
-                },
-                .static => {
-                    try stabs.append(.{
-                        .n_strx = try self.strtab.getOrPut(sym.name),
-                        .n_type = macho.N_STSYM,
-                        .n_sect = reg.section,
-                        .n_desc = 0,
-                        .n_value = reg.address,
-                    });
-                },
-            }
-        }
-
-        // Close the source file!
-        try stabs.append(.{
-            .n_strx = 0,
-            .n_type = macho.N_SO,
-            .n_sect = 0,
-            .n_desc = 0,
-            .n_value = 0,
-        });
-    }
-
-    if (stabs.items.len == 0) return;
-
-    // Write stabs into the symbol table
-    const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
-    const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab;
-
-    symtab.nsyms = @intCast(u32, stabs.items.len);
-
-    const stabs_off = symtab.symoff;
-    const stabs_size = symtab.nsyms * @sizeOf(macho.nlist_64);
-    log.debug("writing symbol stabs from 0x{x} to 0x{x}", .{ stabs_off, stabs_size + stabs_off });
-    try self.file.?.pwriteAll(mem.sliceAsBytes(stabs.items), stabs_off);
-
-    linkedit.inner.filesize += stabs_size;
-
-    // Update dynamic symbol table.
-    const dysymtab = &self.load_commands.items[self.dysymtab_cmd_index.?].Dysymtab;
-    dysymtab.nlocalsym = symtab.nsyms;
-}
-
 fn writeSymbolTable(self: *Zld) !void {
     const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
     const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab;
@@ -2854,6 +2717,15 @@ fn writeSymbolTable(self: *Zld) !void {
     defer exports.deinit();
 
     for (self.objects.items) |object| {
+        for (object.stabs.items) |sym| {
+            const stab = sym.cast(Symbol.Stab) orelse unreachable;
+
+            const nlists = try stab.asNlists(self.allocator, &self.strtab);
+            defer self.allocator.free(nlists);
+
+            try locals.appendSlice(nlists);
+        }
+
         for (object.symbols.items) |sym| {
             const final = sym.getTopmostAlias();
             if (final.@"type" != .regular) continue;