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
logfc81358b75f155aa16ae145de06a30c6ccd30e67
tree1225b9d7ea7c55f621f0298d359c512c76a2d659
parenta4b1a3a0b367912720122791f02289ea4a69cb84

Coff: Progress on loading archives

- Remove unnecessary deferring of registering undef globals - Start parsing linker members

1 files changed, 141 insertions(+), 37 deletions(-)

src/link/Coff.zig+141-37
......@@ -76,6 +76,9 @@ pub const default_size_of_stack_commit: u32 = 0x1000;
7676pub const default_size_of_heap_reserve: u32 = 0x100000;
7777pub const default_size_of_heap_commit: u32 = 0x1000;
7878
79pub const archive_signature = "!<arch>\n";
80pub const archive_end_of_header = "`\n";
81
7982/// This is the start of a Portable Executable (PE) file.
8083/// It starts with a MS-DOS header followed by a MS-DOS stub program.
8184/// This data does not change so we include it as follows in all binaries.
......@@ -494,7 +497,7 @@ pub const Member = struct {
494497 member.content_ni.location(&coff.mf).resolve(&coff.mf)[1],
495498 );
496499
497 @memcpy(&header.end_of_header, "`\n");
500 @memcpy(&header.end_of_header, archive_end_of_header);
498501 }
499502
500503 pub fn storeHeaderDecimalStr(field_ptr: anytype, value: u64) void {
......@@ -507,6 +510,17 @@ pub const Member = struct {
507510 .fill = ' ',
508511 });
509512 }
513
514 pub fn loadHeaderDecimalStr(field_ptr: anytype, value: u64) void {
515 const array_info = @typeInfo(@typeInfo(@TypeOf(field_ptr)).pointer.child).array;
516 assert(array_info.child == u8);
517 assert(value < comptime try std.math.powi(u64, 10, array_info.len));
518 _ = std.fmt.printInt(field_ptr, value, 10, .lower, .{
519 .width = array_info.len,
520 .alignment = .left,
521 .fill = ' ',
522 });
523 }
510524};
511525
512526pub const LongNamesTable = struct {
......@@ -1450,7 +1464,6 @@ fn initHeaders(
14501464 coff.nodes.appendAssumeCapacity(.header);
14511465
14521466 const pe_signature = "PE\x00\x00";
1453 const archive_signature = "!<arch>\n";
14541467
14551468 const signature_ni = Node.known.signature;
14561469 assert(signature_ni == try coff.mf.addLastChildNode(gpa, if (is_image or !is_archive) header_ni else Node.known.file, .{
......@@ -2686,10 +2699,6 @@ fn flushInputSection(coff: *Coff, isi: Node.InputSectionIndex) !void {
26862699 if (try nw.interface.sendFileAll(&fr, .limited(@intCast(file_loc.size))) != file_loc.size)
26872700 return error.EndOfStream;
26882701 si.applyLocationRelocs(coff);
2689
2690 // TODO: Problem is that if the sym is first seen as undef, it's si is in the range of the section
2691 // that wants that symbol. but when the section that contains it is moved, the iteration doesn't see
2692 // that symbol.
26932702}
26942703
26952704fn addSection(coff: *Coff, name: String, flags: std.coff.SectionHeader.Flags) !Symbol.Index {
......@@ -3445,12 +3454,6 @@ fn loadObject(
34453454 var symbols: std.ArrayList(Symbol.Index) = .empty;
34463455 try symbols.ensureUnusedCapacity(gpa, header.number_of_symbols);
34473456
3448 var undefs: std.ArrayList(struct {
3449 symbol_i: u32,
3450 name: String,
3451 size: u32,
3452 }) = .empty;
3453
34543457 const first_si = coff.symbols.items.len;
34553458 var symbol_i: u32 = 0;
34563459 while (symbol_i < header.number_of_symbols) {
......@@ -3554,11 +3557,12 @@ fn loadObject(
35543557 },
35553558 .EXTERNAL => switch (symbol.section_number) {
35563559 .UNDEFINED => {
3557 (try undefs.addOne(gpa)).* = .{
3558 .symbol_i = symbol_i,
3559 .name = try coff.getOrPutString(name),
3560 .size = symbol.value,
3561 };
3560 const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = name });
3561 si_slice[0] = global_gop.value_ptr.*;
3562 if (!global_gop.found_existing) {
3563 const sym = si_slice[0].get(coff);
3564 sym.value = .{ .size = symbol.value };
3565 }
35623566 },
35633567 .ABSOLUTE => return diags.failParse(
35643568 path,
......@@ -3614,18 +3618,6 @@ fn loadObject(
36143618 input.last_si = @enumFromInt(coff.symbols.items.len - 1);
36153619 }
36163620
3617 // These are added after all the defined symbols are created so they are not part of the
3618 // input's symbol range, which should only contain symbols that are actually located in this input.
3619 for (undefs.items) |undef| {
3620 // TODO: Avoid redundant hashing by having name be a union on String / []const u8
3621 const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = undef.name.toSlice(coff) });
3622 symbols.items[undef.symbol_i] = global_gop.value_ptr.*;
3623 if (!global_gop.found_existing) {
3624 const sym = symbols.items[undef.symbol_i].get(coff);
3625 sym.value = .{ .size = undef.size };
3626 }
3627 }
3628
36293621 const relocation_size = std.coff.Relocation.sizeOf();
36303622 for (sections) |section| {
36313623 if (section.si == .null) continue;
......@@ -3663,23 +3655,135 @@ fn loadObject(
36633655 }
36643656}
36653657
3658fn parseArchiveHeader(
3659 header: *const std.coff.ArchiveMemberHeader,
3660 opt_longnames: ?[]const u8,
3661) !struct {
3662 name: []const u8,
3663 size: u34,
3664} {
3665 const trim = std.mem.trimEnd(u8, &header.name, &.{' '});
3666
3667 if (trim.len == 0) return error.BadName;
3668 const name = if (trim[0] == '/') name: {
3669 if (trim.len == 1 or
3670 trim.len == 2 and trim[1] == '/')
3671 break :name trim;
3672
3673 const offset = std.fmt.parseUnsigned(u50, trim[1..], 10) catch
3674 return error.BadName;
3675
3676 if (opt_longnames) |longnames| {
3677 if (offset >= longnames.len) return error.BadName;
3678 break :name std.mem.sliceTo(longnames[offset..], 0);
3679 } else return error.NoLongNames;
3680 } else if (trim[trim.len - 1] == '/')
3681 trim[0 .. trim.len - 1]
3682 else
3683 return error.BadName;
3684
3685 const size = std.fmt.parseUnsigned(u34, std.mem.trimEnd(u8, &header.size, &.{' '}), 10) catch
3686 return error.BadSize;
3687
3688 if (!std.mem.eql(u8, &header.end_of_header, archive_end_of_header))
3689 return error.BadEndOfHeader;
3690
3691 return .{
3692 .name = name,
3693 .size = size,
3694 };
3695}
3696
36663697fn loadArchive(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !void {
36673698 const comp = coff.base.comp;
36683699 const gpa = comp.gpa;
36693700 const diags = &comp.link_diags;
36703701 const r = &fr.interface;
3702 const target_endian = coff.targetEndian();
36713703
36723704 log.debug("loadArchive({f})", .{path.fmtEscapeString()});
36733705
3674 // TODO: Skip over 1st linker member
3675 // TODO: Build index of symbols -> members from 2nd linker member
3676 // TODO: We don't actually have to load an object unless we need a symbol from it (when linking images)
3677 // TODO: Lazily call loadObject whenever a symbol is need from one of the members.
3678 // Could do that in flushGlobal if we haven't gotten an .ni for the symbol yet (and no lib_name)?
3706 const signature = try r.take(archive_signature.len);
3707 if (!std.mem.eql(u8, signature, archive_signature))
3708 return diags.failParse(path, "bad signature", .{});
3709
3710 var opt_expected_kind: ?Member.Kind = .first_linker;
3711 var opt_longnames: ?[]const u8 = null;
3712 defer if (opt_longnames) |l| gpa.free(l);
3713
3714 var pos = fr.logicalPos();
3715 const size = try fr.getSize();
3716 while (pos < size) : (pos = fr.logicalPos()) {
3717 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 };
3724
3725 if (pos + res.size > size)
3726 return diags.failParse(path, "out-of-bounds length 0x{x} in member '{s}'", .{ res.size, res.name });
36793727
3680 _ = gpa;
3681 _ = diags;
3682 _ = r;
3728 log.debug("loadArchiveMember({s})", .{res.name});
3729
3730 if (opt_expected_kind) |expected_kind| expected: switch (expected_kind) {
3731 .first_linker => {
3732 if (!std.mem.eql(u8, res.name, "/"))
3733 return diags.failParse(path, "expected first linker member, found '{s}'", .{res.name});
3734
3735 try fr.seekTo(fr.logicalPos() + res.size);
3736 opt_expected_kind = .second_linker;
3737 continue;
3738 },
3739 .second_linker => {
3740 if (!std.mem.eql(u8, res.name, "/"))
3741 return diags.failParse(path, "expected second linker member, found '{s}'", .{res.name});
3742
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);
3749
3750 opt_expected_kind = .longnames;
3751 continue;
3752 },
3753 .longnames => {
3754 defer opt_expected_kind = null;
3755
3756 // 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;
3760 },
3761 else => unreachable,
3762 };
3763
3764 const member_sig = try r.peek(4);
3765 const machine = std.mem.readInt(u16, member_sig[0..2], target_endian);
3766 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);
3769
3770 // TODO: Validate import table header fields
3771 _ = import_header;
3772 } else {
3773 const coff_header = try r.peekStruct(std.coff.Header, target_endian);
3774
3775 // TODO: Validate COFF header fields
3776 _ = coff_header;
3777 }
3778
3779 try fr.seekTo(fr.logicalPos() + res.size);
3780 }
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 };
36833787}
36843788
36853789fn loadRes(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !void {