authorgravatar for kcbanner@gmail.comCasey Banner <kcbanner@gmail.com> 2026-06-05 01:55:34-04:00
committergravatar for kcbanner@gmail.comCasey Banner <kcbanner@gmail.com> 2026-06-23 00:22:42-04:00
logcef6ea05d982589d29bc312b43bde1432ea78623
tree6dd8eb493c3959e0879992805f69ae52b1715026
parentfc81358b75f155aa16ae145de06a30c6ccd30e67

Coff: Loading input archives

- Build a lookup table for symbols from each loaded archive

1 files changed, 239 insertions(+), 43 deletions(-)

src/link/Coff.zig+239-43
......@@ -31,6 +31,13 @@ long_names_table: LongNamesTable,
3131import_table: ImportTable,
3232export_table: ExportTable,
3333symbol_table: SymbolTable,
34input_archives: std.ArrayList(InputArchive),
35input_archive_members: std.ArrayList(InputArchive.Member),
36input_archive_symbols: std.ArrayList(InputArchive.Member.Symbol),
37input_archive_symbol_indices: std.AutoArrayHashMapUnmanaged(String, struct {
38 first: InputArchive.Member.Symbol.Index,
39 last: InputArchive.Member.Symbol.Index,
40}),
3441inputs: std.ArrayList(Input),
3542input_resolved: std.ArrayList(Symbol.Index),
3643input_sections: std.ArrayList(struct {
......@@ -47,7 +54,6 @@ strings: std.HashMapUnmanaged(
4754),
4855string_bytes: std.ArrayList(u8),
4956section_table: std.AutoArrayHashMapUnmanaged(String, Section),
50tls_si: Symbol.Index,
5157pseudo_section_table: std.array_hash_map.Auto(String, Symbol.Index),
5258object_section_table: std.array_hash_map.Auto(String, Symbol.Index),
5359symbols: std.ArrayList(Symbol),
......@@ -387,6 +393,36 @@ pub const Node = union(enum) {
387393 }
388394};
389395
396pub const InputArchive = struct {
397 path: std.Build.Cache.Path,
398
399 const Index = enum(u32) {
400 _,
401 };
402
403 pub const Member = struct {
404 iai: InputArchive.Index,
405 name: String,
406 // This range includes the member header
407 file_location: MappedFile.Node.FileLocation,
408 is_import: bool,
409 // TODO: Field indicating we loaded it already (ii)
410 const Index = enum(u32) {
411 _,
412 };
413
414 pub const Symbol = struct {
415 iami: InputArchive.Member.Index,
416 // Set to its own index to indicate its the last in the list
417 next: InputArchive.Member.Symbol.Index,
418
419 const Index = enum(u32) {
420 _,
421 };
422 };
423 };
424};
425
390426pub const Input = struct {
391427 path: std.Build.Cache.Path,
392428 archive_name: ?[]const u8,
......@@ -771,6 +807,7 @@ pub const Symbol = struct {
771807 /// Relocations targeting this symbol
772808 target_relocs: Reloc.Index,
773809 section_number: SectionNumber,
810 /// Only used when outputting objects
774811 sti: SymbolTable.Index,
775812 gmi: Node.GlobalMapIndex,
776813 unused0: u16 = 0,
......@@ -1295,6 +1332,10 @@ fn create(
12951332 .pending = .empty,
12961333 .pending_shrink = false,
12971334 },
1335 .input_archives = .empty,
1336 .input_archive_members = .empty,
1337 .input_archive_symbols = .empty,
1338 .input_archive_symbol_indices = .empty,
12981339 .inputs = .empty,
12991340 .input_resolved = .empty,
13001341 .input_sections = .empty,
......@@ -1302,7 +1343,6 @@ fn create(
13021343 .strings = .empty,
13031344 .string_bytes = .empty,
13041345 .section_table = .empty,
1305 .tls_si = .null,
13061346 .pseudo_section_table = .empty,
13071347 .object_section_table = .empty,
13081348 .symbols = .empty,
......@@ -1356,6 +1396,10 @@ pub fn deinit(coff: *Coff) void {
13561396 coff.export_table.entries.deinit(gpa);
13571397 coff.symbol_table.strings.deinit(gpa);
13581398 coff.symbol_table.pending.deinit(gpa);
1399 coff.input_archives.deinit(gpa);
1400 coff.input_archive_members.deinit(gpa);
1401 coff.input_archive_symbols.deinit(gpa);
1402 coff.input_archive_symbol_indices.deinit(gpa);
13591403 coff.inputs.deinit(gpa);
13601404 coff.input_resolved.deinit(gpa);
13611405 coff.input_sections.deinit(gpa);
......@@ -1841,7 +1885,7 @@ fn initHeaders(
18411885
18421886 if (comp.config.any_non_single_threaded) {
18431887 if (!is_image)
1844 coff.tls_si = try coff.addSection(.@".tls$", .{
1888 _ = try coff.addSection(.@".tls$", .{
18451889 .CNT_INITIALIZED_DATA = true,
18461890 .MEM_READ = true,
18471891 .MEM_WRITE = true,
......@@ -3655,13 +3699,29 @@ fn loadObject(
36553699 }
36563700}
36573701
3658fn parseArchiveHeader(
3659 header: *const std.coff.ArchiveMemberHeader,
3660 opt_longnames: ?[]const u8,
3661) !struct {
3702const ArchiveMemberHeader = struct {
36623703 name: []const u8,
36633704 size: u34,
3664} {
3705};
3706
3707fn parseArchiveMemberHeader(
3708 diags: *link.Diags,
3709 path: std.Build.Cache.Path,
3710 header: *const std.coff.ArchiveMemberHeader,
3711 opt_longnames: ?[]const u8,
3712) !ArchiveMemberHeader {
3713 return parseArchiveMemberHeaderInner(header, opt_longnames) catch |err| switch (err) {
3714 error.BadName => return diags.failParse(path, "malformed member header name: '{s}'", .{&header.name}),
3715 error.BadSize => return diags.failParse(path, "malformed member header size: '{s}'", .{&header.size}),
3716 error.BadEndOfHeader => return diags.failParse(path, "bad member header end of header", .{}),
3717 error.NoLongNames => return diags.failParse(path, "long name used without longnames member", .{}),
3718 };
3719}
3720
3721fn parseArchiveMemberHeaderInner(
3722 header: *const std.coff.ArchiveMemberHeader,
3723 opt_longnames: ?[]const u8,
3724) !ArchiveMemberHeader {
36653725 const trim = std.mem.trimEnd(u8, &header.name, &.{' '});
36663726
36673727 if (trim.len == 0) return error.BadName;
......@@ -3711,23 +3771,58 @@ fn loadArchive(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !vo
37113771 var opt_longnames: ?[]const u8 = null;
37123772 defer if (opt_longnames) |l| gpa.free(l);
37133773
3774 var members: std.ArrayList(struct {
3775 offset: u32,
3776 iami: ?InputArchive.Member.Index,
3777 }) = .empty;
3778 var symbol_member_indices: std.ArrayList(u32) = .empty;
3779
3780 const iai: InputArchive.Index = @enumFromInt(coff.input_archives.items.len);
3781 (try coff.input_archives.addOne(gpa)).* = .{
3782 .path = path,
3783 };
3784
3785 const first_iami = coff.input_archive_members.items.len;
3786 const first_iamsi = coff.input_archive_symbols.items.len;
3787 const first_symbol_indices_index = coff.input_archive_symbol_indices.count();
3788
3789 errdefer {
3790 for (coff.input_archive_symbol_indices.values()) |*v| {
3791 if (@intFromEnum(v.last) < first_iamsi) continue;
3792 if (@intFromEnum(v.first) >= first_iamsi) continue;
3793
3794 var iter = v.first;
3795 v.last = while (iter != v.last) {
3796 const sym = &coff.input_archive_symbols.items[@intFromEnum(iter)];
3797 if (@intFromEnum(sym.next) >= first_iamsi) {
3798 sym.next = iter;
3799 break iter;
3800 }
3801
3802 iter = sym.next;
3803 } else unreachable;
3804 }
3805
3806 // New entries in this map will only have pointed to iamsi we also just added
3807 coff.input_archive_symbol_indices.shrinkRetainingCapacity(first_symbol_indices_index);
3808 coff.input_archive_symbols.shrinkRetainingCapacity(first_iamsi);
3809 coff.input_archive_members.shrinkRetainingCapacity(first_iami);
3810 _ = coff.input_archives.pop();
3811 }
3812
37143813 var pos = fr.logicalPos();
37153814 const size = try fr.getSize();
37163815 while (pos < size) : (pos = fr.logicalPos()) {
37173816 const header = try r.takeStruct(std.coff.ArchiveMemberHeader, target_endian);
3718 const res = parseArchiveHeader(&header, opt_longnames) catch |err| switch (err) {
3719 error.BadName => return diags.failParse(path, "malformed member header name: '{s}'", .{&header.name}),
3720 error.BadSize => return diags.failParse(path, "malformed member header size: '{s}'", .{&header.size}),
3721 error.BadEndOfHeader => return diags.failParse(path, "bad member header end of header", .{}),
3722 error.NoLongNames => return diags.failParse(path, "long name used without longnames member", .{}),
3723 };
3817 const res = try parseArchiveMemberHeader(diags, path, &header, opt_longnames);
37243818
3725 if (pos + res.size > size)
3819 const member_end = fr.logicalPos() + res.size;
3820 if (member_end > size)
37263821 return diags.failParse(path, "out-of-bounds length 0x{x} in member '{s}'", .{ res.size, res.name });
37273822
37283823 log.debug("loadArchiveMember({s})", .{res.name});
37293824
3730 if (opt_expected_kind) |expected_kind| expected: switch (expected_kind) {
3825 if (opt_expected_kind) |expected_kind| switch (expected_kind) {
37313826 .first_linker => {
37323827 if (!std.mem.eql(u8, res.name, "/"))
37333828 return diags.failParse(path, "expected first linker member, found '{s}'", .{res.name});
......@@ -3740,50 +3835,142 @@ fn loadArchive(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !vo
37403835 if (!std.mem.eql(u8, res.name, "/"))
37413836 return diags.failParse(path, "expected second linker member, found '{s}'", .{res.name});
37423837
3743 // TODO: Parse this!
3744 // TODO: Build index of symbols -> members from 2nd linker member
3745 // TODO: We don't actually have to load an object unless we need a symbol from it (when linking images)
3746 // TODO: Lazily call loadObject whenever a symbol is need from one of the members.
3747 // Could do that in flushGlobal if we haven't gotten an .ni for the symbol yet (and no lib_name)?
3748 try r.discardAll(res.size);
3838 const num_members = try r.takeInt(u32, target_endian);
3839 pos = fr.logicalPos();
3840 if (pos + num_members * 4 > member_end)
3841 return diags.failParse(path, "invalid member count 0x{x} in second linker member", .{num_members});
3842
3843 try members.ensureTotalCapacity(gpa, num_members);
3844 for (0..num_members) |_|
3845 members.addOneAssumeCapacity().* = .{
3846 .offset = try r.takeInt(u32, target_endian),
3847 .iami = null,
3848 };
3849
3850 const num_symbols = try r.takeInt(u32, target_endian);
3851 pos = fr.logicalPos();
3852 if (pos + num_symbols * 2 > member_end)
3853 return diags.failParse(path, "invalid symbol count 0x{x} in second linker member", .{num_symbols});
3854
3855 try symbol_member_indices.ensureTotalCapacity(gpa, num_symbols);
3856 for (0..num_symbols) |_|
3857 symbol_member_indices.addOneAssumeCapacity().* = (try r.takeInt(u16, target_endian)) - 1;
3858
3859 pos = fr.logicalPos();
3860 try coff.ensureManyUnusedStringCapacity(num_symbols, member_end - pos);
3861 try coff.input_archive_members.ensureUnusedCapacity(gpa, num_members);
3862 try coff.input_archive_symbols.ensureUnusedCapacity(gpa, num_symbols);
3863 try coff.input_archive_symbol_indices.ensureUnusedCapacity(gpa, num_symbols);
3864
3865 var symbol_i: u32 = 0;
3866 while (pos < member_end and symbol_i < num_symbols) : ({
3867 pos = fr.logicalPos();
3868 symbol_i += 1;
3869 }) {
3870 const name = if (r.takeDelimiter(0) catch |err| switch (err) {
3871 error.StreamTooLong => null,
3872 else => |e| return e,
3873 }) |n| n else return diags.failParse(path, "unterminated string found in second linker member", .{});
3874
3875 const string = coff.getOrPutStringAssumeCapacity(name);
3876 const iamsi: InputArchive.Member.Symbol.Index = @enumFromInt(coff.input_archive_symbols.items.len);
3877 const symbol_gop = coff.input_archive_symbol_indices.getOrPutAssumeCapacity(string);
3878 if (!symbol_gop.found_existing) {
3879 symbol_gop.value_ptr.* = .{
3880 .first = iamsi,
3881 .last = iamsi,
3882 };
3883 } else {
3884 coff.input_archive_symbols.items[@intFromEnum(symbol_gop.value_ptr.last)].next = iamsi;
3885 symbol_gop.value_ptr.last = iamsi;
3886 }
3887
3888 const iami = members.items[symbol_member_indices.items[symbol_i]].iami orelse iami: {
3889 const iami: InputArchive.Member.Index = @enumFromInt(coff.input_archive_members.items.len);
3890 const member_offset = members.items[symbol_member_indices.items[symbol_i]].offset;
3891 coff.input_archive_members.addOneAssumeCapacity().* = .{
3892 .iai = iai,
3893 .name = undefined,
3894 .is_import = undefined,
3895 .file_location = .{
3896 .offset = member_offset,
3897 .size = undefined,
3898 },
3899 };
3900
3901 members.items[symbol_member_indices.items[symbol_i]].iami = iami;
3902 break :iami iami;
3903 };
3904
3905 log.debug("loadArchiveMemberSymbol({s}) = ({d}, {d}, {d})", .{ name, iai, iami, iamsi });
3906
3907 coff.input_archive_symbols.addOneAssumeCapacity().* = .{
3908 .iami = iami,
3909 .next = iamsi,
3910 };
3911 }
3912
3913 if (symbol_i != num_symbols)
3914 return diags.failParse(
3915 path,
3916 " expected {d} entries in second linker member string table, but found {d}",
3917 .{ num_symbols, symbol_i },
3918 );
37493919
3920 try fr.seekTo(member_end);
37503921 opt_expected_kind = .longnames;
37513922 continue;
37523923 },
37533924 .longnames => {
3754 defer opt_expected_kind = null;
3755
37563925 // This member is optional
3757 if (!std.mem.eql(u8, res.name, "//")) break :expected;
3758 opt_longnames = try r.readAlloc(gpa, res.size);
3759 continue;
3926 if (std.mem.eql(u8, res.name, "//"))
3927 opt_longnames = try r.readAlloc(gpa, res.size);
3928
3929 opt_expected_kind = null;
3930 break;
37603931 },
37613932 else => unreachable,
37623933 };
3934 }
3935
3936 if (opt_expected_kind) |expected_kind| switch (expected_kind) {
3937 .first_linker => return diags.failParse(path, "missing first linker member", .{}),
3938 .second_linker => return diags.failParse(path, "missing second linker member", .{}),
3939 else => {},
3940 };
3941
3942 // Validate / read names and sizes of all the referenced members
3943 for (coff.input_archive_members.items[first_iami..]) |*member| {
3944 try fr.seekTo(member.file_location.offset);
3945
3946 const header = try r.takeStruct(std.coff.ArchiveMemberHeader, target_endian);
3947 const res = try parseArchiveMemberHeader(diags, path, &header, opt_longnames);
3948
3949 try coff.ensureUnusedStringCapacity(res.name.len);
3950 member.name = coff.getOrPutStringAssumeCapacity(res.name);
37633951
37643952 const member_sig = try r.peek(4);
37653953 const machine = std.mem.readInt(u16, member_sig[0..2], target_endian);
37663954 const sig = std.mem.readInt(u16, member_sig[2..4], target_endian);
3767 if (machine == @intFromEnum(std.coff.IMAGE.FILE.MACHINE.UNKNOWN) and sig == 0xffff) {
3768 const import_header = try r.peekStruct(std.coff.ImportHeader, target_endian);
3955 member.is_import = machine == @intFromEnum(std.coff.IMAGE.FILE.MACHINE.UNKNOWN) and sig == 0xffff;
3956 member.file_location.size = res.size;
37693957
3770 // TODO: Validate import table header fields
3771 _ = import_header;
3772 } else {
3773 const coff_header = try r.peekStruct(std.coff.Header, target_endian);
3958 log.debug("verifyArchiveMember({s}) = 0x{x}+{x}", .{
3959 res.name,
3960 member.file_location.offset,
3961 member.file_location.size,
3962 });
37743963
3775 // TODO: Validate COFF header fields
3776 _ = coff_header;
3964 if (member.is_import) {
3965 const import_header = try r.peekStruct(std.coff.ImportHeader, target_endian);
3966 // TODO: Validate import table header fields
3967 // TODO: Use this result in flushGlobal
3968 return diags.failParse(path, "TODO implement parsing import headers: {t} {t}", .{
3969 import_header.types.type,
3970 import_header.types.name_type,
3971 });
37773972 }
3778
3779 try fr.seekTo(fr.logicalPos() + res.size);
37803973 }
3781
3782 if (opt_expected_kind) |expected_kind| switch (expected_kind) {
3783 .first_linker => return diags.failParse(path, "missing first linker member", .{}),
3784 .second_linker => return diags.failParse(path, "missing second linker member", .{}),
3785 else => {},
3786 };
37873974}
37883975
37893976fn loadRes(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !void {
......@@ -4723,6 +4910,15 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !void {
47234910 coff.nodes.appendAssumeCapacity(.{ .global = gmi });
47244911 sym.rva = coff.computeNodeRva(sym.ni);
47254912 si.applyLocationRelocs(coff);
4913 } else {
4914
4915 // TODO: Check if not defined, and if in an input member
4916 // TODO: If so, queue a loadObject for that member
4917 // TODO: Return a value indicating to retry this flushGlobal
4918 // TODO: The loadObject idle task should be before the flushGlobal idle task
4919 //
4920 // TODO: Check if we can get flushGlobal before prelink, that would cause a problem
4921
47264922 }
47274923}
47284924