Commit bcf387f0b9

Jacob Young <jacobly0@users.noreply.github.com>
2025-06-06 06:45:00
Elf: support non-comdat groups
I haven't actually found any documentation about these, but apparently groups aren't always comdats.
1 parent 25da0f8
src/link/Elf/file.zig
@@ -198,10 +198,10 @@ pub const File = union(enum) {
         };
     }
 
-    pub fn comdatGroup(file: File, ind: Elf.ComdatGroup.Index) *Elf.ComdatGroup {
+    pub fn group(file: File, ind: Elf.Group.Index) *Elf.Group {
         return switch (file) {
             .linker_defined, .shared_object, .zig_object => unreachable,
-            .object => |x| x.comdatGroup(ind),
+            .object => |x| x.group(ind),
         };
     }
 
src/link/Elf/Object.zig
@@ -20,8 +20,8 @@ atoms: std.ArrayListUnmanaged(Atom) = .empty,
 atoms_indexes: std.ArrayListUnmanaged(Atom.Index) = .empty,
 atoms_extra: std.ArrayListUnmanaged(u32) = .empty,
 
-comdat_groups: std.ArrayListUnmanaged(Elf.ComdatGroup) = .empty,
-comdat_group_data: std.ArrayListUnmanaged(u32) = .empty,
+groups: std.ArrayListUnmanaged(Elf.Group) = .empty,
+group_data: std.ArrayListUnmanaged(u32) = .empty,
 
 input_merge_sections: std.ArrayListUnmanaged(Merge.InputSection) = .empty,
 input_merge_sections_indexes: std.ArrayListUnmanaged(Merge.InputSection.Index) = .empty,
@@ -49,8 +49,8 @@ pub fn deinit(self: *Object, gpa: Allocator) void {
     self.atoms.deinit(gpa);
     self.atoms_indexes.deinit(gpa);
     self.atoms_extra.deinit(gpa);
-    self.comdat_groups.deinit(gpa);
-    self.comdat_group_data.deinit(gpa);
+    self.groups.deinit(gpa);
+    self.group_data.deinit(gpa);
     self.relocs.deinit(gpa);
     self.fdes.deinit(gpa);
     self.cies.deinit(gpa);
@@ -304,22 +304,22 @@ fn initAtoms(
                 }
                 const group_members = @as([*]align(1) const u32, @ptrCast(group_raw_data.ptr))[0..group_nmembers];
 
-                if (group_members[0] != elf.GRP_COMDAT) {
-                    return diags.failParse(path, "corrupt section group: unknown SHT_GROUP format", .{});
+                switch (group_members[0]) {
+                    0, elf.GRP_COMDAT => {
+                        const group_start: u32 = @intCast(self.group_data.items.len);
+                        try self.group_data.appendUnalignedSlice(gpa, group_members[1..]);
+
+                        self.group(try self.addGroup(gpa)).* = .{
+                            .signature_off = group_signature,
+                            .file_index = self.index,
+                            .shndx = shndx,
+                            .members_start = group_start,
+                            .members_len = @intCast(group_nmembers - 1),
+                            .is_comdat = group_members[0] == elf.GRP_COMDAT,
+                        };
+                    },
+                    else => return diags.failParse(path, "corrupt section group: unknown SHT_GROUP format", .{}),
                 }
-
-                const group_start: u32 = @intCast(self.comdat_group_data.items.len);
-                try self.comdat_group_data.appendUnalignedSlice(gpa, group_members[1..]);
-
-                const comdat_group_index = try self.addComdatGroup(gpa);
-                const comdat_group = self.comdatGroup(comdat_group_index);
-                comdat_group.* = .{
-                    .signature_off = group_signature,
-                    .file_index = self.index,
-                    .shndx = shndx,
-                    .members_start = group_start,
-                    .members_len = @intCast(group_nmembers - 1),
-                };
             },
 
             elf.SHT_SYMTAB_SHNDX => @panic("TODO SHT_SYMTAB_SHNDX"),
@@ -986,28 +986,28 @@ pub fn convertCommonSymbols(self: *Object, elf_file: *Elf) !void {
     }
 }
 
-pub fn resolveComdatGroups(self: *Object, elf_file: *Elf, table: anytype) !void {
-    for (self.comdat_groups.items, 0..) |*cg, cgi| {
-        const signature = cg.signature(elf_file);
+pub fn resolveGroups(self: *Object, elf_file: *Elf, table: anytype) !void {
+    for (self.groups.items, 0..) |*g, gi| {
+        const signature = g.signature(elf_file);
         const gop = try table.getOrPut(signature);
         if (!gop.found_existing) {
-            gop.value_ptr.* = .{ .index = @intCast(cgi), .file = self.index };
+            gop.value_ptr.* = .{ .index = @intCast(gi), .file = self.index };
             continue;
         }
-        const current = elf_file.comdatGroup(gop.value_ptr.*);
-        cg.alive = false;
+        const current = elf_file.group(gop.value_ptr.*);
+        g.alive = false;
         if (self.index < current.file_index) {
             current.alive = false;
-            cg.alive = true;
-            gop.value_ptr.* = .{ .index = @intCast(cgi), .file = self.index };
+            g.alive = true;
+            gop.value_ptr.* = .{ .index = @intCast(gi), .file = self.index };
         }
     }
 }
 
-pub fn markComdatGroupsDead(self: *Object, elf_file: *Elf) void {
-    for (self.comdat_groups.items) |cg| {
-        if (cg.alive) continue;
-        for (cg.comdatGroupMembers(elf_file)) |shndx| {
+pub fn markGroupsDead(self: *Object, elf_file: *Elf) void {
+    for (self.groups.items) |g| {
+        if (g.alive) continue;
+        for (g.members(elf_file)) |shndx| {
             const atom_index = self.atoms_indexes.items[shndx];
             if (self.atom(atom_index)) |atom_ptr| {
                 atom_ptr.alive = false;
@@ -1421,15 +1421,15 @@ fn inputMergeSection(self: *Object, index: Merge.InputSection.Index) ?*Merge.Inp
     return &self.input_merge_sections.items[index];
 }
 
-fn addComdatGroup(self: *Object, gpa: Allocator) !Elf.ComdatGroup.Index {
-    const index = @as(Elf.ComdatGroup.Index, @intCast(self.comdat_groups.items.len));
-    _ = try self.comdat_groups.addOne(gpa);
+fn addGroup(self: *Object, gpa: Allocator) !Elf.Group.Index {
+    const index: Elf.Group.Index = @intCast(self.groups.items.len);
+    _ = try self.groups.addOne(gpa);
     return index;
 }
 
-pub fn comdatGroup(self: *Object, index: Elf.ComdatGroup.Index) *Elf.ComdatGroup {
-    assert(index < self.comdat_groups.items.len);
-    return &self.comdat_groups.items[index];
+pub fn group(self: *Object, index: Elf.Group.Index) *Elf.Group {
+    assert(index < self.groups.items.len);
+    return &self.groups.items[index];
 }
 
 pub fn format(
@@ -1550,14 +1550,14 @@ fn formatFdes(
     }
 }
 
-pub fn fmtComdatGroups(self: *Object, elf_file: *Elf) std.fmt.Formatter(formatComdatGroups) {
+pub fn fmtGroups(self: *Object, elf_file: *Elf) std.fmt.Formatter(formatGroups) {
     return .{ .data = .{
         .object = self,
         .elf_file = elf_file,
     } };
 }
 
-fn formatComdatGroups(
+fn formatGroups(
     ctx: FormatContext,
     comptime unused_fmt_string: []const u8,
     options: std.fmt.FormatOptions,
@@ -1567,13 +1567,13 @@ fn formatComdatGroups(
     _ = options;
     const object = ctx.object;
     const elf_file = ctx.elf_file;
-    try writer.writeAll("  COMDAT groups\n");
-    for (object.comdat_groups.items, 0..) |cg, cg_index| {
-        try writer.print("    COMDAT({d})", .{cg_index});
-        if (!cg.alive) try writer.writeAll(" : [*]");
+    try writer.writeAll("  groups\n");
+    for (object.groups.items, 0..) |g, g_index| {
+        try writer.print("    {s}({d})", .{ if (g.is_comdat) "COMDAT" else "GROUP", g_index });
+        if (!g.alive) try writer.writeAll(" : [*]");
         try writer.writeByte('\n');
-        const cg_members = cg.comdatGroupMembers(elf_file);
-        for (cg_members) |shndx| {
+        const g_members = g.members(elf_file);
+        for (g_members) |shndx| {
             const atom_index = object.atoms_indexes.items[shndx];
             const atom_ptr = object.atom(atom_index) orelse continue;
             try writer.print("      atom({d}) : {s}\n", .{ atom_index, atom_ptr.name(elf_file) });
src/link/Elf/relocatable.zig
@@ -19,7 +19,7 @@ pub fn flushStaticLib(elf_file: *Elf, comp: *Compilation) !void {
             &elf_file.sections,
             elf_file.shstrtab.items,
             elf_file.merge_sections.items,
-            elf_file.comdat_group_sections.items,
+            elf_file.group_sections.items,
             elf_file.zigObjectPtr(),
             elf_file.files,
         );
@@ -152,7 +152,7 @@ pub fn flushObject(elf_file: *Elf, comp: *Compilation) !void {
         &elf_file.sections,
         elf_file.shstrtab.items,
         elf_file.merge_sections.items,
-        elf_file.comdat_group_sections.items,
+        elf_file.group_sections.items,
         elf_file.zigObjectPtr(),
         elf_file.files,
     );
@@ -233,19 +233,19 @@ fn initSections(elf_file: *Elf) !void {
             );
     }
 
-    try initComdatGroups(elf_file);
+    try initGroups(elf_file);
     try elf_file.initSymtab();
     try elf_file.initShStrtab();
 }
 
-fn initComdatGroups(elf_file: *Elf) !void {
+fn initGroups(elf_file: *Elf) !void {
     const gpa = elf_file.base.comp.gpa;
 
     for (elf_file.objects.items) |index| {
         const object = elf_file.file(index).?.object;
-        for (object.comdat_groups.items, 0..) |cg, cg_index| {
+        for (object.groups.items, 0..) |cg, cg_index| {
             if (!cg.alive) continue;
-            const cg_sec = try elf_file.comdat_group_sections.addOne(gpa);
+            const cg_sec = try elf_file.group_sections.addOne(gpa);
             cg_sec.* = .{
                 .shndx = try elf_file.addSection(.{
                     .name = try elf_file.insertShString(".group"),
@@ -292,12 +292,12 @@ fn updateSectionSizes(elf_file: *Elf) !void {
     }
 
     try elf_file.updateSymtabSize();
-    updateComdatGroupsSizes(elf_file);
+    updateGroupsSizes(elf_file);
     elf_file.updateShStrtabSize();
 }
 
-fn updateComdatGroupsSizes(elf_file: *Elf) void {
-    for (elf_file.comdat_group_sections.items) |cg| {
+fn updateGroupsSizes(elf_file: *Elf) void {
+    for (elf_file.group_sections.items) |cg| {
         const shdr = &elf_file.sections.items(.shdr)[cg.shndx];
         shdr.sh_size = cg.size(elf_file);
         shdr.sh_link = elf_file.section_indexes.symtab.?;
@@ -436,21 +436,21 @@ fn writeSyntheticSections(elf_file: *Elf) !void {
         try elf_file.base.file.?.pwriteAll(mem.sliceAsBytes(relocs.items), shdr.sh_offset);
     }
 
-    try writeComdatGroups(elf_file);
+    try writeGroups(elf_file);
     try elf_file.writeSymtab();
     try elf_file.writeShStrtab();
 }
 
-fn writeComdatGroups(elf_file: *Elf) !void {
+fn writeGroups(elf_file: *Elf) !void {
     const gpa = elf_file.base.comp.gpa;
-    for (elf_file.comdat_group_sections.items) |cgs| {
+    for (elf_file.group_sections.items) |cgs| {
         const shdr = elf_file.sections.items(.shdr)[cgs.shndx];
         const sh_size = math.cast(usize, shdr.sh_size) orelse return error.Overflow;
         var buffer = try std.ArrayList(u8).initCapacity(gpa, sh_size);
         defer buffer.deinit();
         try cgs.write(elf_file, buffer.writer());
         assert(buffer.items.len == sh_size);
-        log.debug("writing COMDAT group from 0x{x} to 0x{x}", .{
+        log.debug("writing group from 0x{x} to 0x{x}", .{
             shdr.sh_offset,
             shdr.sh_offset + shdr.sh_size,
         });
src/link/Elf/synthetic_sections.zig
@@ -1484,33 +1484,33 @@ pub const VerneedSection = struct {
     }
 };
 
-pub const ComdatGroupSection = struct {
+pub const GroupSection = struct {
     shndx: u32,
     cg_ref: Elf.Ref,
 
-    fn comdatGroup(cgs: ComdatGroupSection, elf_file: *Elf) *Elf.ComdatGroup {
+    fn group(cgs: GroupSection, elf_file: *Elf) *Elf.Group {
         const cg_file = elf_file.file(cgs.cg_ref.file).?;
-        return cg_file.object.comdatGroup(cgs.cg_ref.index);
+        return cg_file.object.group(cgs.cg_ref.index);
     }
 
-    pub fn symbol(cgs: ComdatGroupSection, elf_file: *Elf) *Symbol {
-        const cg = cgs.comdatGroup(elf_file);
+    pub fn symbol(cgs: GroupSection, elf_file: *Elf) *Symbol {
+        const cg = cgs.group(elf_file);
         const object = cg.file(elf_file).object;
         const shdr = object.shdrs.items[cg.shndx];
         return &object.symbols.items[shdr.sh_info];
     }
 
-    pub fn size(cgs: ComdatGroupSection, elf_file: *Elf) usize {
-        const cg = cgs.comdatGroup(elf_file);
-        const members = cg.comdatGroupMembers(elf_file);
+    pub fn size(cgs: GroupSection, elf_file: *Elf) usize {
+        const cg = cgs.group(elf_file);
+        const members = cg.members(elf_file);
         return (members.len + 1) * @sizeOf(u32);
     }
 
-    pub fn write(cgs: ComdatGroupSection, elf_file: *Elf, writer: anytype) !void {
-        const cg = cgs.comdatGroup(elf_file);
+    pub fn write(cgs: GroupSection, elf_file: *Elf, writer: anytype) !void {
+        const cg = cgs.group(elf_file);
         const object = cg.file(elf_file).object;
-        const members = cg.comdatGroupMembers(elf_file);
-        try writer.writeInt(u32, elf.GRP_COMDAT, .little);
+        const members = cg.members(elf_file);
+        try writer.writeInt(u32, if (cg.is_comdat) elf.GRP_COMDAT else 0, .little);
         for (members) |shndx| {
             const shdr = object.shdrs.items[shndx];
             switch (shdr.sh_type) {
src/link/Elf.zig
@@ -99,7 +99,7 @@ copy_rel: CopyRelSection = .{},
 rela_plt: std.ArrayListUnmanaged(elf.Elf64_Rela) = .empty,
 /// SHT_GROUP sections
 /// Applies only to a relocatable.
-comdat_group_sections: std.ArrayListUnmanaged(ComdatGroupSection) = .empty,
+group_sections: std.ArrayListUnmanaged(GroupSection) = .empty,
 
 resolver: SymbolResolver = .{},
 
@@ -510,7 +510,7 @@ pub fn deinit(self: *Elf) void {
     self.copy_rel.deinit(gpa);
     self.rela_dyn.deinit(gpa);
     self.rela_plt.deinit(gpa);
-    self.comdat_group_sections.deinit(gpa);
+    self.group_sections.deinit(gpa);
     self.dump_argv_list.deinit(gpa);
 }
 
@@ -919,7 +919,7 @@ fn flushModuleInner(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id) !void {
         &self.sections,
         self.shstrtab.items,
         self.merge_sections.items,
-        self.comdat_group_sections.items,
+        self.group_sections.items,
         self.zigObjectPtr(),
         self.files,
     );
@@ -1315,16 +1315,16 @@ pub fn resolveSymbols(self: *Elf) !void {
     }
 
     {
-        // Dedup comdat groups.
+        // Dedup groups.
         var table = std.StringHashMap(Ref).init(self.base.comp.gpa);
         defer table.deinit();
 
         for (self.objects.items) |index| {
-            try self.file(index).?.object.resolveComdatGroups(self, &table);
+            try self.file(index).?.object.resolveGroups(self, &table);
         }
 
         for (self.objects.items) |index| {
-            self.file(index).?.object.markComdatGroupsDead(self);
+            self.file(index).?.object.markGroupsDead(self);
         }
     }
 
@@ -3125,7 +3125,7 @@ pub fn sortShdrs(
     sections: *std.MultiArrayList(Section),
     shstrtab: []const u8,
     merge_sections: []Merge.Section,
-    comdat_group_sections: []ComdatGroupSection,
+    comdat_group_sections: []GroupSection,
     zig_object_ptr: ?*ZigObject,
     files: std.MultiArrayList(File.Entry),
 ) !void {
@@ -4446,8 +4446,8 @@ pub fn atom(self: *Elf, ref: Ref) ?*Atom {
     return file_ptr.atom(ref.index);
 }
 
-pub fn comdatGroup(self: *Elf, ref: Ref) *ComdatGroup {
-    return self.file(ref.file).?.comdatGroup(ref.index);
+pub fn group(self: *Elf, ref: Ref) *Group {
+    return self.file(ref.file).?.group(ref.index);
 }
 
 pub fn symbol(self: *Elf, ref: Ref) ?*Symbol {
@@ -4814,7 +4814,7 @@ fn fmtDumpState(
             object.fmtCies(self),
             object.fmtFdes(self),
             object.fmtSymtab(self),
-            object.fmtComdatGroups(self),
+            object.fmtGroups(self),
         });
     }
 
@@ -4852,9 +4852,9 @@ fn fmtDumpState(
     try writer.print("{}\n", .{self.got.fmt(self)});
     try writer.print("{}\n", .{self.plt.fmt(self)});
 
-    try writer.writeAll("Output COMDAT groups\n");
-    for (self.comdat_group_sections.items) |cg| {
-        try writer.print("  shdr({d}) : COMDAT({})\n", .{ cg.shndx, cg.cg_ref });
+    try writer.writeAll("Output groups\n");
+    for (self.group_sections.items) |cg| {
+        try writer.print("  shdr({d}) : GROUP({})\n", .{ cg.shndx, cg.cg_ref });
     }
 
     try writer.writeAll("\nOutput merge sections\n");
@@ -4934,25 +4934,26 @@ const default_entry_addr = 0x8000000;
 
 pub const base_tag: link.File.Tag = .elf;
 
-pub const ComdatGroup = struct {
+pub const Group = struct {
     signature_off: u32,
     file_index: File.Index,
     shndx: u32,
     members_start: u32,
     members_len: u32,
+    is_comdat: bool,
     alive: bool = true,
 
-    pub fn file(cg: ComdatGroup, elf_file: *Elf) File {
+    pub fn file(cg: Group, elf_file: *Elf) File {
         return elf_file.file(cg.file_index).?;
     }
 
-    pub fn signature(cg: ComdatGroup, elf_file: *Elf) [:0]const u8 {
+    pub fn signature(cg: Group, elf_file: *Elf) [:0]const u8 {
         return cg.file(elf_file).object.getString(cg.signature_off);
     }
 
-    pub fn comdatGroupMembers(cg: ComdatGroup, elf_file: *Elf) []const u32 {
+    pub fn members(cg: Group, elf_file: *Elf) []const u32 {
         const object = cg.file(elf_file).object;
-        return object.comdat_group_data.items[cg.members_start..][0..cg.members_len];
+        return object.group_data.items[cg.members_start..][0..cg.members_len];
     }
 
     pub const Index = u32;
@@ -5310,7 +5311,7 @@ const Air = @import("../Air.zig");
 const Archive = @import("Elf/Archive.zig");
 const AtomList = @import("Elf/AtomList.zig");
 const Compilation = @import("../Compilation.zig");
-const ComdatGroupSection = synthetic_sections.ComdatGroupSection;
+const GroupSection = synthetic_sections.GroupSection;
 const CopyRelSection = synthetic_sections.CopyRelSection;
 const Diags = @import("../link.zig").Diags;
 const DynamicSection = synthetic_sections.DynamicSection;