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;...@@ -76,6 +76,9 @@ pub const default_size_of_stack_commit: u32 = 0x1000;
76pub const default_size_of_heap_reserve: u32 = 0x100000;76pub const default_size_of_heap_reserve: u32 = 0x100000;
77pub const default_size_of_heap_commit: u32 = 0x1000;77pub const default_size_of_heap_commit: u32 = 0x1000;
7878
79pub const archive_signature = "!<arch>\n";
80pub const archive_end_of_header = "`\n";
81
79/// This is the start of a Portable Executable (PE) file.82/// This is the start of a Portable Executable (PE) file.
80/// It starts with a MS-DOS header followed by a MS-DOS stub program.83/// It starts with a MS-DOS header followed by a MS-DOS stub program.
81/// This data does not change so we include it as follows in all binaries.84/// This data does not change so we include it as follows in all binaries.
...@@ -494,7 +497,7 @@ pub const Member = struct {...@@ -494,7 +497,7 @@ pub const Member = struct {
494 member.content_ni.location(&coff.mf).resolve(&coff.mf)[1],497 member.content_ni.location(&coff.mf).resolve(&coff.mf)[1],
495 );498 );
496499
497 @memcpy(&header.end_of_header, "`\n");500 @memcpy(&header.end_of_header, archive_end_of_header);
498 }501 }
499502
500 pub fn storeHeaderDecimalStr(field_ptr: anytype, value: u64) void {503 pub fn storeHeaderDecimalStr(field_ptr: anytype, value: u64) void {
...@@ -507,6 +510,17 @@ pub const Member = struct {...@@ -507,6 +510,17 @@ pub const Member = struct {
507 .fill = ' ',510 .fill = ' ',
508 });511 });
509 }512 }
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 }
510};524};
511525
512pub const LongNamesTable = struct {526pub const LongNamesTable = struct {
...@@ -1450,7 +1464,6 @@ fn initHeaders(...@@ -1450,7 +1464,6 @@ fn initHeaders(
1450 coff.nodes.appendAssumeCapacity(.header);1464 coff.nodes.appendAssumeCapacity(.header);
14511465
1452 const pe_signature = "PE\x00\x00";1466 const pe_signature = "PE\x00\x00";
1453 const archive_signature = "!<arch>\n";
14541467
1455 const signature_ni = Node.known.signature;1468 const signature_ni = Node.known.signature;
1456 assert(signature_ni == try coff.mf.addLastChildNode(gpa, if (is_image or !is_archive) header_ni else Node.known.file, .{1469 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 {...@@ -2686,10 +2699,6 @@ fn flushInputSection(coff: *Coff, isi: Node.InputSectionIndex) !void {
2686 if (try nw.interface.sendFileAll(&fr, .limited(@intCast(file_loc.size))) != file_loc.size)2699 if (try nw.interface.sendFileAll(&fr, .limited(@intCast(file_loc.size))) != file_loc.size)
2687 return error.EndOfStream;2700 return error.EndOfStream;
2688 si.applyLocationRelocs(coff);2701 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.
2693}2702}
26942703
2695fn addSection(coff: *Coff, name: String, flags: std.coff.SectionHeader.Flags) !Symbol.Index {2704fn addSection(coff: *Coff, name: String, flags: std.coff.SectionHeader.Flags) !Symbol.Index {
...@@ -3445,12 +3454,6 @@ fn loadObject(...@@ -3445,12 +3454,6 @@ fn loadObject(
3445 var symbols: std.ArrayList(Symbol.Index) = .empty;3454 var symbols: std.ArrayList(Symbol.Index) = .empty;
3446 try symbols.ensureUnusedCapacity(gpa, header.number_of_symbols);3455 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
3454 const first_si = coff.symbols.items.len;3457 const first_si = coff.symbols.items.len;
3455 var symbol_i: u32 = 0;3458 var symbol_i: u32 = 0;
3456 while (symbol_i < header.number_of_symbols) {3459 while (symbol_i < header.number_of_symbols) {
...@@ -3554,11 +3557,12 @@ fn loadObject(...@@ -3554,11 +3557,12 @@ fn loadObject(
3554 },3557 },
3555 .EXTERNAL => switch (symbol.section_number) {3558 .EXTERNAL => switch (symbol.section_number) {
3556 .UNDEFINED => {3559 .UNDEFINED => {
3557 (try undefs.addOne(gpa)).* = .{3560 const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = name });
3558 .symbol_i = symbol_i,3561 si_slice[0] = global_gop.value_ptr.*;
3559 .name = try coff.getOrPutString(name),3562 if (!global_gop.found_existing) {
3560 .size = symbol.value,3563 const sym = si_slice[0].get(coff);
3561 };3564 sym.value = .{ .size = symbol.value };
3565 }
3562 },3566 },
3563 .ABSOLUTE => return diags.failParse(3567 .ABSOLUTE => return diags.failParse(
3564 path,3568 path,
...@@ -3614,18 +3618,6 @@ fn loadObject(...@@ -3614,18 +3618,6 @@ fn loadObject(
3614 input.last_si = @enumFromInt(coff.symbols.items.len - 1);3618 input.last_si = @enumFromInt(coff.symbols.items.len - 1);
3615 }3619 }
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
3629 const relocation_size = std.coff.Relocation.sizeOf();3621 const relocation_size = std.coff.Relocation.sizeOf();
3630 for (sections) |section| {3622 for (sections) |section| {
3631 if (section.si == .null) continue;3623 if (section.si == .null) continue;
...@@ -3663,23 +3655,135 @@ fn loadObject(...@@ -3663,23 +3655,135 @@ fn loadObject(
3663 }3655 }
3664}3656}
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
3666fn loadArchive(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !void {3697fn loadArchive(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !void {
3667 const comp = coff.base.comp;3698 const comp = coff.base.comp;
3668 const gpa = comp.gpa;3699 const gpa = comp.gpa;
3669 const diags = &comp.link_diags;3700 const diags = &comp.link_diags;
3670 const r = &fr.interface;3701 const r = &fr.interface;
3702 const target_endian = coff.targetEndian();
36713703
3672 log.debug("loadArchive({f})", .{path.fmtEscapeString()});3704 log.debug("loadArchive({f})", .{path.fmtEscapeString()});
36733705
3674 // TODO: Skip over 1st linker member3706 const signature = try r.take(archive_signature.len);
3675 // TODO: Build index of symbols -> members from 2nd linker member3707 if (!std.mem.eql(u8, signature, archive_signature))
3676 // TODO: We don't actually have to load an object unless we need a symbol from it (when linking images)3708 return diags.failParse(path, "bad signature", .{});
3677 // TODO: Lazily call loadObject whenever a symbol is need from one of the members.3709
3678 // Could do that in flushGlobal if we haven't gotten an .ni for the symbol yet (and no lib_name)?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;3728 log.debug("loadArchiveMember({s})", .{res.name});
3681 _ = diags;3729
3682 _ = r;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 };
3683}3787}
36843788
3685fn loadRes(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !void {3789fn loadRes(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !void {