| ... | ... | @@ -76,6 +76,9 @@ pub const default_size_of_stack_commit: u32 = 0x1000; |
| 76 | 76 | pub const default_size_of_heap_reserve: u32 = 0x100000; |
| 77 | 77 | pub const default_size_of_heap_commit: u32 = 0x1000; |
| 78 | 78 | |
| 79 | pub const archive_signature = "!<arch>\n"; |
| 80 | pub const archive_end_of_header = "`\n"; |
| 81 | |
| 79 | 82 | /// This is the start of a Portable Executable (PE) file. |
| 80 | 83 | /// It starts with a MS-DOS header followed by a MS-DOS stub program. |
| 81 | 84 | /// This data does not change so we include it as follows in all binaries. |
| ... | ... | @@ -494,7 +497,7 @@ pub const Member = struct { |
| 494 | 497 | member.content_ni.location(&coff.mf).resolve(&coff.mf)[1], |
| 495 | 498 | ); |
| 496 | 499 | |
| 497 | | @memcpy(&header.end_of_header, "`\n"); |
| 500 | @memcpy(&header.end_of_header, archive_end_of_header); |
| 498 | 501 | } |
| 499 | 502 | |
| 500 | 503 | pub fn storeHeaderDecimalStr(field_ptr: anytype, value: u64) void { |
| ... | ... | @@ -507,6 +510,17 @@ pub const Member = struct { |
| 507 | 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 | }; |
| 511 | 525 | |
| 512 | 526 | pub const LongNamesTable = struct { |
| ... | ... | @@ -1450,7 +1464,6 @@ fn initHeaders( |
| 1450 | 1464 | coff.nodes.appendAssumeCapacity(.header); |
| 1451 | 1465 | |
| 1452 | 1466 | const pe_signature = "PE\x00\x00"; |
| 1453 | | const archive_signature = "!<arch>\n"; |
| 1454 | 1467 | |
| 1455 | 1468 | const signature_ni = Node.known.signature; |
| 1456 | 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 | 2699 | if (try nw.interface.sendFileAll(&fr, .limited(@intCast(file_loc.size))) != file_loc.size) |
| 2687 | 2700 | return error.EndOfStream; |
| 2688 | 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 | } |
| 2694 | 2703 | |
| 2695 | 2704 | fn addSection(coff: *Coff, name: String, flags: std.coff.SectionHeader.Flags) !Symbol.Index { |
| ... | ... | @@ -3445,12 +3454,6 @@ fn loadObject( |
| 3445 | 3454 | var symbols: std.ArrayList(Symbol.Index) = .empty; |
| 3446 | 3455 | try symbols.ensureUnusedCapacity(gpa, header.number_of_symbols); |
| 3447 | 3456 | |
| 3448 | | var undefs: std.ArrayList(struct { |
| 3449 | | symbol_i: u32, |
| 3450 | | name: String, |
| 3451 | | size: u32, |
| 3452 | | }) = .empty; |
| 3453 | | |
| 3454 | 3457 | const first_si = coff.symbols.items.len; |
| 3455 | 3458 | var symbol_i: u32 = 0; |
| 3456 | 3459 | while (symbol_i < header.number_of_symbols) { |
| ... | ... | @@ -3554,11 +3557,12 @@ fn loadObject( |
| 3554 | 3557 | }, |
| 3555 | 3558 | .EXTERNAL => switch (symbol.section_number) { |
| 3556 | 3559 | .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 | } |
| 3562 | 3566 | }, |
| 3563 | 3567 | .ABSOLUTE => return diags.failParse( |
| 3564 | 3568 | path, |
| ... | ... | @@ -3614,18 +3618,6 @@ fn loadObject( |
| 3614 | 3618 | input.last_si = @enumFromInt(coff.symbols.items.len - 1); |
| 3615 | 3619 | } |
| 3616 | 3620 | |
| 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 | 3621 | const relocation_size = std.coff.Relocation.sizeOf(); |
| 3630 | 3622 | for (sections) |section| { |
| 3631 | 3623 | if (section.si == .null) continue; |
| ... | ... | @@ -3663,23 +3655,135 @@ fn loadObject( |
| 3663 | 3655 | } |
| 3664 | 3656 | } |
| 3665 | 3657 | |
| 3658 | fn 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 | |
| 3666 | 3697 | fn loadArchive(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !void { |
| 3667 | 3698 | const comp = coff.base.comp; |
| 3668 | 3699 | const gpa = comp.gpa; |
| 3669 | 3700 | const diags = &comp.link_diags; |
| 3670 | 3701 | const r = &fr.interface; |
| 3702 | const target_endian = coff.targetEndian(); |
| 3671 | 3703 | |
| 3672 | 3704 | log.debug("loadArchive({f})", .{path.fmtEscapeString()}); |
| 3673 | 3705 | |
| 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 }); |
| 3679 | 3727 | |
| 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 | }; |
| 3683 | 3787 | } |
| 3684 | 3788 | |
| 3685 | 3789 | fn loadRes(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !void { |