master
   1const std = @import("std");
   2const def = @import("def.zig");
   3const Allocator = std.mem.Allocator;
   4
   5// LLVM has some quirks/bugs around padding/size values.
   6// Emulating those quirks made it much easier to test this implementation against the LLVM
   7// implementation since we could just check if the .lib files are byte-for-byte identical.
   8// This remains set to true out of an abundance of caution.
   9const llvm_compat = true;
  10
  11pub const WriteCoffArchiveError = error{TooManyMembers} || std.Io.Writer.Error || std.mem.Allocator.Error;
  12
  13pub fn writeCoffArchive(
  14    allocator: std.mem.Allocator,
  15    writer: *std.Io.Writer,
  16    members: Members,
  17) WriteCoffArchiveError!void {
  18    // The second linker member of a COFF archive uses a 32-bit integer for the number of members field,
  19    // but only 16-bit integers for the "array of 1-based indexes that map symbol names to archive
  20    // member offsets." This means that the maximum number of *indexable* members is maxInt(u16) - 1.
  21    if (members.list.items.len > std.math.maxInt(u16) - 1) return error.TooManyMembers;
  22
  23    try writer.writeAll(archive_start);
  24
  25    const member_offsets = try allocator.alloc(usize, members.list.items.len);
  26    defer allocator.free(member_offsets);
  27    {
  28        var offset: usize = 0;
  29        for (member_offsets, 0..) |*elem, i| {
  30            elem.* = offset;
  31            offset += archive_header_len;
  32            offset += members.list.items[i].byteLenWithPadding();
  33        }
  34    }
  35
  36    var long_names: StringTable = .{};
  37    defer long_names.deinit(allocator);
  38
  39    var symbol_to_member_index = std.StringArrayHashMap(usize).init(allocator);
  40    defer symbol_to_member_index.deinit();
  41    var string_table_len: usize = 0;
  42    var num_symbols: usize = 0;
  43
  44    for (members.list.items, 0..) |member, i| {
  45        for (member.symbol_names_for_import_lib) |symbol_name| {
  46            const gop_result = try symbol_to_member_index.getOrPut(symbol_name);
  47            // When building the symbol map, ignore duplicate symbol names.
  48            // This can happen in cases like (using .def file syntax):
  49            //  _foo
  50            //  foo == _foo
  51            if (gop_result.found_existing) continue;
  52
  53            gop_result.value_ptr.* = i;
  54            string_table_len += symbol_name.len + 1;
  55            num_symbols += 1;
  56        }
  57
  58        if (member.needsLongName()) {
  59            _ = try long_names.put(allocator, member.name);
  60        }
  61    }
  62
  63    const first_linker_member_len = 4 + (4 * num_symbols) + string_table_len;
  64    const second_linker_member_len = 4 + (4 * members.list.items.len) + 4 + (2 * num_symbols) + string_table_len;
  65    const long_names_len_including_header_and_padding = blk: {
  66        if (long_names.map.count() == 0) break :blk 0;
  67        break :blk archive_header_len + std.mem.alignForward(usize, long_names.data.items.len, 2);
  68    };
  69    const first_member_offset = archive_start.len + archive_header_len + std.mem.alignForward(usize, first_linker_member_len, 2) + archive_header_len + std.mem.alignForward(usize, second_linker_member_len, 2) + long_names_len_including_header_and_padding;
  70
  71    // https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#first-linker-member
  72    try writeArchiveMemberHeader(writer, .linker_member, memberHeaderLen(first_linker_member_len), "0");
  73    try writer.writeInt(u32, @intCast(num_symbols), .big);
  74    for (symbol_to_member_index.values()) |member_i| {
  75        const offset = member_offsets[member_i];
  76        try writer.writeInt(u32, @intCast(first_member_offset + offset), .big);
  77    }
  78    for (symbol_to_member_index.keys()) |symbol_name| {
  79        try writer.writeAll(symbol_name);
  80        try writer.writeByte(0);
  81    }
  82    if (first_linker_member_len % 2 != 0) try writer.writeByte(if (llvm_compat) 0 else archive_pad_byte);
  83
  84    // https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#second-linker-member
  85    try writeArchiveMemberHeader(writer, .linker_member, memberHeaderLen(second_linker_member_len), "0");
  86    try writer.writeInt(u32, @intCast(members.list.items.len), .little);
  87    for (member_offsets) |offset| {
  88        try writer.writeInt(u32, @intCast(first_member_offset + offset), .little);
  89    }
  90    try writer.writeInt(u32, @intCast(num_symbols), .little);
  91
  92    // sort lexicographically
  93    const C = struct {
  94        keys: []const []const u8,
  95
  96        pub fn lessThan(ctx: @This(), a_index: usize, b_index: usize) bool {
  97            return std.mem.lessThan(u8, ctx.keys[a_index], ctx.keys[b_index]);
  98        }
  99    };
 100    symbol_to_member_index.sortUnstable(C{ .keys = symbol_to_member_index.keys() });
 101
 102    for (symbol_to_member_index.values()) |member_i| {
 103        try writer.writeInt(u16, @intCast(member_i + 1), .little);
 104    }
 105    for (symbol_to_member_index.keys()) |symbol_name| {
 106        try writer.writeAll(symbol_name);
 107        try writer.writeByte(0);
 108    }
 109    if (first_linker_member_len % 2 != 0) try writer.writeByte(if (llvm_compat) 0 else archive_pad_byte);
 110
 111    // https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#longnames-member
 112    if (long_names.data.items.len != 0) {
 113        const written_len = long_names.data.items.len;
 114        try writeLongNamesMemberHeader(writer, memberHeaderLen(written_len));
 115        try writer.writeAll(long_names.data.items);
 116        if (long_names.data.items.len % 2 != 0) try writer.writeByte(archive_pad_byte);
 117    }
 118
 119    for (members.list.items) |member| {
 120        const name: MemberName = if (member.needsLongName())
 121            .{ .longname = long_names.getOffset(member.name).? }
 122        else
 123            .{ .name = member.name };
 124        try writeArchiveMemberHeader(writer, name, member.bytes.len, "644");
 125        try writer.writeAll(member.bytes);
 126        if (member.bytes.len % 2 != 0) try writer.writeByte(archive_pad_byte);
 127    }
 128
 129    try writer.flush();
 130}
 131
 132const archive_start = "!<arch>\n";
 133const archive_header_end = "`\n";
 134const archive_pad_byte = '\n';
 135const archive_header_len = 60;
 136
 137fn memberHeaderLen(len: usize) usize {
 138    return if (llvm_compat)
 139        // LLVM writes this with the padding byte included, likely a bug/mistake
 140        std.mem.alignForward(usize, len, 2)
 141    else
 142        len;
 143}
 144
 145const MemberName = union(enum) {
 146    name: []const u8,
 147    linker_member,
 148    longnames_member,
 149    longname: usize,
 150
 151    pub fn write(self: MemberName, writer: *std.Io.Writer) !void {
 152        switch (self) {
 153            .name => |name| {
 154                try writer.writeAll(name);
 155                try writer.writeByte('/');
 156                try writer.splatByteAll(' ', 16 - (name.len + 1));
 157            },
 158            .linker_member => {
 159                try writer.writeAll("/               ");
 160            },
 161            .longnames_member => {
 162                try writer.writeAll("//              ");
 163            },
 164            .longname => |offset| {
 165                try writer.print("/{d: <15}", .{offset});
 166            },
 167        }
 168    }
 169};
 170
 171fn writeLongNamesMemberHeader(writer: *std.Io.Writer, size: usize) !void {
 172    try (MemberName{ .longnames_member = {} }).write(writer);
 173    try writer.splatByteAll(' ', archive_header_len - 16 - 10 - archive_header_end.len);
 174    try writer.print("{d: <10}", .{size});
 175    try writer.writeAll(archive_header_end);
 176}
 177
 178fn writeArchiveMemberHeader(writer: *std.Io.Writer, name: MemberName, size: usize, mode: []const u8) !void {
 179    try name.write(writer);
 180    try writer.writeAll("0           "); // date
 181    try writer.writeAll("0     "); // user id
 182    try writer.writeAll("0     "); // group id
 183    try writer.print("{s: <8}", .{mode}); // mode
 184    try writer.print("{d: <10}", .{size});
 185    try writer.writeAll(archive_header_end);
 186}
 187
 188pub const Members = struct {
 189    list: std.ArrayList(Member) = .empty,
 190    arena: std.heap.ArenaAllocator,
 191
 192    pub const Member = struct {
 193        bytes: []const u8,
 194        name: []const u8,
 195        symbol_names_for_import_lib: []const []const u8,
 196
 197        pub fn byteLenWithPadding(self: Member) usize {
 198            return std.mem.alignForward(usize, self.bytes.len, 2);
 199        }
 200
 201        pub fn needsLongName(self: Member) bool {
 202            return self.name.len >= 16;
 203        }
 204    };
 205
 206    pub fn deinit(self: *const Members) void {
 207        self.arena.deinit();
 208    }
 209};
 210
 211const GetMembersError = GetImportDescriptorError || GetShortImportError;
 212
 213pub fn getMembers(
 214    allocator: std.mem.Allocator,
 215    module_def: def.ModuleDefinition,
 216    machine_type: std.coff.IMAGE.FILE.MACHINE,
 217) GetMembersError!Members {
 218    var members: Members = .{
 219        .arena = std.heap.ArenaAllocator.init(allocator),
 220    };
 221    const arena = members.arena.allocator();
 222    errdefer members.deinit();
 223
 224    try members.list.ensureTotalCapacity(arena, 3 + module_def.exports.items.len);
 225    const module_import_name = try arena.dupe(u8, module_def.name orelse "");
 226    const library = std.fs.path.stem(module_import_name);
 227
 228    const import_descriptor_symbol_name = try std.mem.concat(arena, u8, &.{
 229        import_descriptor_prefix,
 230        library,
 231    });
 232    const null_thunk_symbol_name = try std.mem.concat(arena, u8, &.{
 233        null_thunk_data_prefix,
 234        library,
 235        null_thunk_data_suffix,
 236    });
 237
 238    members.list.appendAssumeCapacity(try getImportDescriptor(arena, machine_type, module_import_name, import_descriptor_symbol_name, null_thunk_symbol_name));
 239    members.list.appendAssumeCapacity(try getNullImportDescriptor(arena, machine_type, module_import_name));
 240    members.list.appendAssumeCapacity(try getNullThunk(arena, machine_type, module_import_name, null_thunk_symbol_name));
 241
 242    const DeferredExport = struct {
 243        name: []const u8,
 244        e: *const def.ModuleDefinition.Export,
 245    };
 246    var renames: std.ArrayList(DeferredExport) = .empty;
 247    defer renames.deinit(allocator);
 248    var regular_imports: std.StringArrayHashMapUnmanaged([]const u8) = .empty;
 249    defer regular_imports.deinit(allocator);
 250
 251    for (module_def.exports.items) |*e| {
 252        if (e.private) continue;
 253
 254        const maybe_mangled_name = e.mangled_symbol_name orelse e.name;
 255        const name = maybe_mangled_name;
 256
 257        if (e.ext_name) |ext_name| {
 258            _ = ext_name;
 259            @panic("TODO"); // impossible if fixupForImportLibraryGeneration is called
 260        }
 261
 262        var import_name_type: std.coff.ImportNameType = undefined;
 263        var export_name: ?[]const u8 = null;
 264        if (e.no_name) {
 265            import_name_type = .ORDINAL;
 266        } else if (e.export_as) |export_as| {
 267            import_name_type = .NAME_EXPORTAS;
 268            export_name = export_as;
 269        } else if (e.import_name) |import_name| {
 270            if (machine_type == .I386 and std.mem.eql(u8, applyNameType(.NAME_UNDECORATE, name), import_name)) {
 271                import_name_type = .NAME_UNDECORATE;
 272            } else if (machine_type == .I386 and std.mem.eql(u8, applyNameType(.NAME_NOPREFIX, name), import_name)) {
 273                import_name_type = .NAME_NOPREFIX;
 274            } else if (isArm64EC(machine_type)) {
 275                import_name_type = .NAME_EXPORTAS;
 276                export_name = import_name;
 277            } else if (std.mem.eql(u8, name, import_name)) {
 278                import_name_type = .NAME;
 279            } else {
 280                try renames.append(allocator, .{
 281                    .name = name,
 282                    .e = e,
 283                });
 284                continue;
 285            }
 286        } else {
 287            import_name_type = getNameType(maybe_mangled_name, e.name, machine_type, module_def.type);
 288        }
 289
 290        try regular_imports.put(allocator, applyNameType(import_name_type, name), name);
 291        try members.list.append(arena, try getShortImport(arena, module_import_name, name, export_name, machine_type, e.ordinal, e.type, import_name_type));
 292    }
 293    for (renames.items) |deferred| {
 294        const import_name = deferred.e.import_name.?;
 295        if (regular_imports.get(import_name)) |symbol| {
 296            if (deferred.e.type == .CODE) {
 297                try members.list.append(arena, try getWeakExternal(arena, module_import_name, symbol, deferred.name, .{
 298                    .imp_prefix = false,
 299                    .machine_type = machine_type,
 300                }));
 301            }
 302            try members.list.append(arena, try getWeakExternal(arena, module_import_name, symbol, deferred.name, .{
 303                .imp_prefix = true,
 304                .machine_type = machine_type,
 305            }));
 306        } else {
 307            try members.list.append(arena, try getShortImport(
 308                arena,
 309                module_import_name,
 310                deferred.name,
 311                deferred.e.import_name,
 312                machine_type,
 313                deferred.e.ordinal,
 314                deferred.e.type,
 315                .NAME_EXPORTAS,
 316            ));
 317        }
 318    }
 319
 320    return members;
 321}
 322
 323/// Returns a slice of `name`
 324fn applyNameType(name_type: std.coff.ImportNameType, name: []const u8) []const u8 {
 325    switch (name_type) {
 326        .NAME_NOPREFIX, .NAME_UNDECORATE => {
 327            if (name.len == 0) return name;
 328            const unprefixed = switch (name[0]) {
 329                '?', '@', '_' => name[1..],
 330                else => name,
 331            };
 332            if (name_type == .NAME_UNDECORATE) {
 333                var split = std.mem.splitScalar(u8, unprefixed, '@');
 334                return split.first();
 335            } else {
 336                return unprefixed;
 337            }
 338        },
 339        else => return name,
 340    }
 341}
 342
 343fn getNameType(
 344    symbol: []const u8,
 345    ext_name: []const u8,
 346    machine_type: std.coff.IMAGE.FILE.MACHINE,
 347    module_definition_type: def.ModuleDefinitionType,
 348) std.coff.ImportNameType {
 349    // A decorated stdcall function in MSVC is exported with the
 350    // type IMPORT_NAME, and the exported function name includes the
 351    // the leading underscore. In MinGW on the other hand, a decorated
 352    // stdcall function still omits the underscore (IMPORT_NAME_NOPREFIX).
 353    if (std.mem.startsWith(u8, ext_name, "_") and
 354        std.mem.indexOfScalar(u8, ext_name, '@') != null and
 355        module_definition_type != .mingw)
 356        return .NAME;
 357    if (!std.mem.eql(u8, symbol, ext_name))
 358        return .NAME_UNDECORATE;
 359    if (machine_type == .I386 and std.mem.startsWith(u8, symbol, "_"))
 360        return .NAME_NOPREFIX;
 361    return .NAME;
 362}
 363
 364fn is64Bit(machine_type: std.coff.IMAGE.FILE.MACHINE) bool {
 365    return switch (machine_type) {
 366        .AMD64, .ARM64, .ARM64EC, .ARM64X => true,
 367        else => false,
 368    };
 369}
 370
 371fn isArm64EC(machine_type: std.coff.IMAGE.FILE.MACHINE) bool {
 372    return switch (machine_type) {
 373        .ARM64EC, .ARM64X => true,
 374        else => false,
 375    };
 376}
 377
 378const null_import_descriptor_symbol_name = "__NULL_IMPORT_DESCRIPTOR";
 379const import_descriptor_prefix = "__IMPORT_DESCRIPTOR_";
 380const null_thunk_data_prefix = "\x7F";
 381const null_thunk_data_suffix = "_NULL_THUNK_DATA";
 382
 383// past the string table length field
 384const first_string_table_entry_offset = @sizeOf(u32);
 385const first_string_table_entry = getNameBytesForStringTableOffset(first_string_table_entry_offset);
 386
 387const byte_size_of_relocation = 10;
 388
 389fn getNameBytesForStringTableOffset(offset: u32) [8]u8 {
 390    var bytes = [_]u8{0} ** 8;
 391    std.mem.writeInt(u32, bytes[4..8], offset, .little);
 392    return bytes;
 393}
 394
 395const GetImportDescriptorError = error{UnsupportedMachineType} || std.mem.Allocator.Error;
 396
 397fn getImportDescriptor(
 398    allocator: std.mem.Allocator,
 399    machine_type: std.coff.IMAGE.FILE.MACHINE,
 400    module_import_name: []const u8,
 401    import_descriptor_symbol_name: []const u8,
 402    null_thunk_symbol_name: []const u8,
 403) GetImportDescriptorError!Members.Member {
 404    const number_of_sections = 2;
 405    const number_of_symbols = 7;
 406    const number_of_relocations = 3;
 407
 408    const pointer_to_idata2_data = @sizeOf(std.coff.Header) +
 409        (@sizeOf(std.coff.SectionHeader) * number_of_sections);
 410    const pointer_to_idata6_data = pointer_to_idata2_data +
 411        @sizeOf(std.coff.ImportDirectoryEntry) +
 412        (byte_size_of_relocation * number_of_relocations);
 413    const pointer_to_symbol_table = pointer_to_idata6_data +
 414        module_import_name.len + 1;
 415
 416    const string_table_byte_len = 4 +
 417        (import_descriptor_symbol_name.len + 1) +
 418        (null_import_descriptor_symbol_name.len + 1) +
 419        (null_thunk_symbol_name.len + 1);
 420    const total_byte_len = pointer_to_symbol_table +
 421        (std.coff.Symbol.sizeOf() * number_of_symbols) +
 422        string_table_byte_len;
 423
 424    const bytes = try allocator.alloc(u8, total_byte_len);
 425    errdefer allocator.free(bytes);
 426    var writer: std.Io.Writer = .fixed(bytes);
 427
 428    writer.writeStruct(std.coff.Header{
 429        .machine = machine_type,
 430        .number_of_sections = number_of_sections,
 431        .time_date_stamp = 0,
 432        .pointer_to_symbol_table = @intCast(pointer_to_symbol_table),
 433        .number_of_symbols = number_of_symbols,
 434        .size_of_optional_header = 0,
 435        .flags = .{ .@"32BIT_MACHINE" = !is64Bit(machine_type) },
 436    }, .little) catch unreachable;
 437
 438    writer.writeStruct(std.coff.SectionHeader{
 439        .name = ".idata$2".*,
 440        .virtual_size = 0,
 441        .virtual_address = 0,
 442        .size_of_raw_data = @sizeOf(std.coff.ImportDirectoryEntry),
 443        .pointer_to_raw_data = pointer_to_idata2_data,
 444        .pointer_to_relocations = pointer_to_idata2_data + @sizeOf(std.coff.ImportDirectoryEntry),
 445        .pointer_to_linenumbers = 0,
 446        .number_of_relocations = number_of_relocations,
 447        .number_of_linenumbers = 0,
 448        .flags = .{
 449            .ALIGN = .@"4BYTES",
 450            .CNT_INITIALIZED_DATA = true,
 451            .MEM_WRITE = true,
 452            .MEM_READ = true,
 453        },
 454    }, .little) catch unreachable;
 455
 456    writer.writeStruct(std.coff.SectionHeader{
 457        .name = ".idata$6".*,
 458        .virtual_size = 0,
 459        .virtual_address = 0,
 460        .size_of_raw_data = @intCast(module_import_name.len + 1),
 461        .pointer_to_raw_data = pointer_to_idata6_data,
 462        .pointer_to_relocations = 0,
 463        .pointer_to_linenumbers = 0,
 464        .number_of_relocations = 0,
 465        .number_of_linenumbers = 0,
 466        .flags = .{
 467            .ALIGN = .@"2BYTES",
 468            .CNT_INITIALIZED_DATA = true,
 469            .MEM_WRITE = true,
 470            .MEM_READ = true,
 471        },
 472    }, .little) catch unreachable;
 473
 474    // .idata$2
 475    writer.writeStruct(std.coff.ImportDirectoryEntry{
 476        .forwarder_chain = 0,
 477        .import_address_table_rva = 0,
 478        .import_lookup_table_rva = 0,
 479        .name_rva = 0,
 480        .time_date_stamp = 0,
 481    }, .little) catch unreachable;
 482
 483    const relocation_rva_type = rvaRelocationTypeIndicator(machine_type) orelse return error.UnsupportedMachineType;
 484    writeRelocation(&writer, .{
 485        .virtual_address = @offsetOf(std.coff.ImportDirectoryEntry, "name_rva"),
 486        .symbol_table_index = 2,
 487        .type = relocation_rva_type,
 488    }) catch unreachable;
 489    writeRelocation(&writer, .{
 490        .virtual_address = @offsetOf(std.coff.ImportDirectoryEntry, "import_lookup_table_rva"),
 491        .symbol_table_index = 3,
 492        .type = relocation_rva_type,
 493    }) catch unreachable;
 494    writeRelocation(&writer, .{
 495        .virtual_address = @offsetOf(std.coff.ImportDirectoryEntry, "import_address_table_rva"),
 496        .symbol_table_index = 4,
 497        .type = relocation_rva_type,
 498    }) catch unreachable;
 499
 500    // .idata$6
 501    writer.writeAll(module_import_name) catch unreachable;
 502    writer.writeByte(0) catch unreachable;
 503
 504    var string_table_offset: usize = first_string_table_entry_offset;
 505    writeSymbol(&writer, .{
 506        .name = first_string_table_entry,
 507        .value = 0,
 508        .section_number = @enumFromInt(1),
 509        .type = .{
 510            .base_type = .NULL,
 511            .complex_type = .NULL,
 512        },
 513        .storage_class = .EXTERNAL,
 514        .number_of_aux_symbols = 0,
 515    }) catch unreachable;
 516    string_table_offset += import_descriptor_symbol_name.len + 1;
 517    writeSymbol(&writer, .{
 518        .name = ".idata$2".*,
 519        .value = 0,
 520        .section_number = @enumFromInt(1),
 521        .type = .{
 522            .base_type = .NULL,
 523            .complex_type = .NULL,
 524        },
 525        .storage_class = .SECTION,
 526        .number_of_aux_symbols = 0,
 527    }) catch unreachable;
 528    writeSymbol(&writer, .{
 529        .name = ".idata$6".*,
 530        .value = 0,
 531        .section_number = @enumFromInt(2),
 532        .type = .{
 533            .base_type = .NULL,
 534            .complex_type = .NULL,
 535        },
 536        .storage_class = .STATIC,
 537        .number_of_aux_symbols = 0,
 538    }) catch unreachable;
 539    writeSymbol(&writer, .{
 540        .name = ".idata$4".*,
 541        .value = 0,
 542        .section_number = .UNDEFINED,
 543        .type = .{
 544            .base_type = .NULL,
 545            .complex_type = .NULL,
 546        },
 547        .storage_class = .SECTION,
 548        .number_of_aux_symbols = 0,
 549    }) catch unreachable;
 550    writeSymbol(&writer, .{
 551        .name = ".idata$5".*,
 552        .value = 0,
 553        .section_number = .UNDEFINED,
 554        .type = .{
 555            .base_type = .NULL,
 556            .complex_type = .NULL,
 557        },
 558        .storage_class = .SECTION,
 559        .number_of_aux_symbols = 0,
 560    }) catch unreachable;
 561    writeSymbol(&writer, .{
 562        .name = getNameBytesForStringTableOffset(@intCast(string_table_offset)),
 563        .value = 0,
 564        .section_number = .UNDEFINED,
 565        .type = .{
 566            .base_type = .NULL,
 567            .complex_type = .NULL,
 568        },
 569        .storage_class = .EXTERNAL,
 570        .number_of_aux_symbols = 0,
 571    }) catch unreachable;
 572    string_table_offset += null_import_descriptor_symbol_name.len + 1;
 573    writeSymbol(&writer, .{
 574        .name = getNameBytesForStringTableOffset(@intCast(string_table_offset)),
 575        .value = 0,
 576        .section_number = .UNDEFINED,
 577        .type = .{
 578            .base_type = .NULL,
 579            .complex_type = .NULL,
 580        },
 581        .storage_class = .EXTERNAL,
 582        .number_of_aux_symbols = 0,
 583    }) catch unreachable;
 584    string_table_offset += null_thunk_symbol_name.len + 1;
 585
 586    // string table
 587    writer.writeInt(u32, @intCast(string_table_byte_len), .little) catch unreachable;
 588    writer.writeAll(import_descriptor_symbol_name) catch unreachable;
 589    writer.writeByte(0) catch unreachable;
 590    writer.writeAll(null_import_descriptor_symbol_name) catch unreachable;
 591    writer.writeByte(0) catch unreachable;
 592    writer.writeAll(null_thunk_symbol_name) catch unreachable;
 593    writer.writeByte(0) catch unreachable;
 594
 595    var symbol_names_for_import_lib = try allocator.alloc([]const u8, 1);
 596    errdefer allocator.free(symbol_names_for_import_lib);
 597
 598    const duped_symbol_name = try allocator.dupe(u8, import_descriptor_symbol_name);
 599    errdefer allocator.free(duped_symbol_name);
 600    symbol_names_for_import_lib[0] = duped_symbol_name;
 601
 602    // Confirm byte length was calculated exactly correctly
 603    std.debug.assert(writer.end == bytes.len);
 604    return .{
 605        .bytes = bytes,
 606        .name = module_import_name,
 607        .symbol_names_for_import_lib = symbol_names_for_import_lib,
 608    };
 609}
 610
 611fn getNullImportDescriptor(
 612    allocator: std.mem.Allocator,
 613    machine_type: std.coff.IMAGE.FILE.MACHINE,
 614    module_import_name: []const u8,
 615) error{OutOfMemory}!Members.Member {
 616    const number_of_sections = 1;
 617    const number_of_symbols = 1;
 618    const pointer_to_idata3_data = @sizeOf(std.coff.Header) +
 619        (@sizeOf(std.coff.SectionHeader) * number_of_sections);
 620    const pointer_to_symbol_table = pointer_to_idata3_data +
 621        @sizeOf(std.coff.ImportDirectoryEntry);
 622
 623    const string_table_byte_len = 4 + null_import_descriptor_symbol_name.len + 1;
 624    const total_byte_len = pointer_to_symbol_table +
 625        (std.coff.Symbol.sizeOf() * number_of_symbols) +
 626        string_table_byte_len;
 627
 628    const bytes = try allocator.alloc(u8, total_byte_len);
 629    errdefer allocator.free(bytes);
 630    var writer: std.Io.Writer = .fixed(bytes);
 631
 632    writer.writeStruct(std.coff.Header{
 633        .machine = machine_type,
 634        .number_of_sections = number_of_sections,
 635        .time_date_stamp = 0,
 636        .pointer_to_symbol_table = @intCast(pointer_to_symbol_table),
 637        .number_of_symbols = number_of_symbols,
 638        .size_of_optional_header = 0,
 639        .flags = .{ .@"32BIT_MACHINE" = !is64Bit(machine_type) },
 640    }, .little) catch unreachable;
 641
 642    writer.writeStruct(std.coff.SectionHeader{
 643        .name = ".idata$3".*,
 644        .virtual_size = 0,
 645        .virtual_address = 0,
 646        .size_of_raw_data = @sizeOf(std.coff.ImportDirectoryEntry),
 647        .pointer_to_raw_data = pointer_to_idata3_data,
 648        .pointer_to_relocations = 0,
 649        .pointer_to_linenumbers = 0,
 650        .number_of_relocations = 0,
 651        .number_of_linenumbers = 0,
 652        .flags = .{
 653            .ALIGN = .@"4BYTES",
 654            .CNT_INITIALIZED_DATA = true,
 655            .MEM_WRITE = true,
 656            .MEM_READ = true,
 657        },
 658    }, .little) catch unreachable;
 659
 660    writer.writeStruct(std.coff.ImportDirectoryEntry{
 661        .forwarder_chain = 0,
 662        .import_address_table_rva = 0,
 663        .import_lookup_table_rva = 0,
 664        .name_rva = 0,
 665        .time_date_stamp = 0,
 666    }, .little) catch unreachable;
 667
 668    writeSymbol(&writer, .{
 669        .name = first_string_table_entry,
 670        .value = 0,
 671        .section_number = @enumFromInt(1),
 672        .type = .{
 673            .base_type = .NULL,
 674            .complex_type = .NULL,
 675        },
 676        .storage_class = .EXTERNAL,
 677        .number_of_aux_symbols = 0,
 678    }) catch unreachable;
 679
 680    // string table
 681    writer.writeInt(u32, string_table_byte_len, .little) catch unreachable;
 682    writer.writeAll(null_import_descriptor_symbol_name) catch unreachable;
 683    writer.writeByte(0) catch unreachable;
 684
 685    var symbol_names_for_import_lib = try allocator.alloc([]const u8, 1);
 686    errdefer allocator.free(symbol_names_for_import_lib);
 687
 688    const duped_symbol_name = try allocator.dupe(u8, null_import_descriptor_symbol_name);
 689    errdefer allocator.free(duped_symbol_name);
 690    symbol_names_for_import_lib[0] = duped_symbol_name;
 691
 692    // Confirm byte length was calculated exactly correctly
 693    std.debug.assert(writer.end == bytes.len);
 694    return .{
 695        .bytes = bytes,
 696        .name = module_import_name,
 697        .symbol_names_for_import_lib = symbol_names_for_import_lib,
 698    };
 699}
 700
 701fn getNullThunk(
 702    allocator: std.mem.Allocator,
 703    machine_type: std.coff.IMAGE.FILE.MACHINE,
 704    module_import_name: []const u8,
 705    null_thunk_symbol_name: []const u8,
 706) error{OutOfMemory}!Members.Member {
 707    const number_of_sections = 2;
 708    const number_of_symbols = 1;
 709    const va_size: u32 = if (is64Bit(machine_type)) 8 else 4;
 710    const pointer_to_idata5_data = @sizeOf(std.coff.Header) +
 711        (@sizeOf(std.coff.SectionHeader) * number_of_sections);
 712    const pointer_to_idata4_data = pointer_to_idata5_data + va_size;
 713    const pointer_to_symbol_table = pointer_to_idata4_data + va_size;
 714
 715    const string_table_byte_len = 4 + null_thunk_symbol_name.len + 1;
 716    const total_byte_len = pointer_to_symbol_table +
 717        (std.coff.Symbol.sizeOf() * number_of_symbols) +
 718        string_table_byte_len;
 719
 720    const bytes = try allocator.alloc(u8, total_byte_len);
 721    errdefer allocator.free(bytes);
 722    var writer: std.Io.Writer = .fixed(bytes);
 723
 724    writer.writeStruct(std.coff.Header{
 725        .machine = machine_type,
 726        .number_of_sections = number_of_sections,
 727        .time_date_stamp = 0,
 728        .pointer_to_symbol_table = @intCast(pointer_to_symbol_table),
 729        .number_of_symbols = number_of_symbols,
 730        .size_of_optional_header = 0,
 731        .flags = .{ .@"32BIT_MACHINE" = !is64Bit(machine_type) },
 732    }, .little) catch unreachable;
 733
 734    writer.writeStruct(std.coff.SectionHeader{
 735        .name = ".idata$5".*,
 736        .virtual_size = 0,
 737        .virtual_address = 0,
 738        .size_of_raw_data = va_size,
 739        .pointer_to_raw_data = pointer_to_idata5_data,
 740        .pointer_to_relocations = 0,
 741        .pointer_to_linenumbers = 0,
 742        .number_of_relocations = 0,
 743        .number_of_linenumbers = 0,
 744        .flags = .{
 745            .ALIGN = if (is64Bit(machine_type))
 746                .@"8BYTES"
 747            else
 748                .@"4BYTES",
 749            .CNT_INITIALIZED_DATA = true,
 750            .MEM_WRITE = true,
 751            .MEM_READ = true,
 752        },
 753    }, .little) catch unreachable;
 754
 755    writer.writeStruct(std.coff.SectionHeader{
 756        .name = ".idata$4".*,
 757        .virtual_size = 0,
 758        .virtual_address = 0,
 759        .size_of_raw_data = va_size,
 760        .pointer_to_raw_data = pointer_to_idata4_data,
 761        .pointer_to_relocations = 0,
 762        .pointer_to_linenumbers = 0,
 763        .number_of_relocations = 0,
 764        .number_of_linenumbers = 0,
 765        .flags = .{
 766            .ALIGN = if (is64Bit(machine_type))
 767                .@"8BYTES"
 768            else
 769                .@"4BYTES",
 770            .CNT_INITIALIZED_DATA = true,
 771            .MEM_WRITE = true,
 772            .MEM_READ = true,
 773        },
 774    }, .little) catch unreachable;
 775
 776    // .idata$5
 777    writer.splatByteAll(0, va_size) catch unreachable;
 778    // .idata$4
 779    writer.splatByteAll(0, va_size) catch unreachable;
 780
 781    writeSymbol(&writer, .{
 782        .name = first_string_table_entry,
 783        .value = 0,
 784        .section_number = @enumFromInt(1),
 785        .type = .{
 786            .base_type = .NULL,
 787            .complex_type = .NULL,
 788        },
 789        .storage_class = .EXTERNAL,
 790        .number_of_aux_symbols = 0,
 791    }) catch unreachable;
 792
 793    // string table
 794    writer.writeInt(u32, @intCast(string_table_byte_len), .little) catch unreachable;
 795    writer.writeAll(null_thunk_symbol_name) catch unreachable;
 796    writer.writeByte(0) catch unreachable;
 797
 798    var symbol_names_for_import_lib = try allocator.alloc([]const u8, 1);
 799    errdefer allocator.free(symbol_names_for_import_lib);
 800
 801    const duped_symbol_name = try allocator.dupe(u8, null_thunk_symbol_name);
 802    errdefer allocator.free(duped_symbol_name);
 803    symbol_names_for_import_lib[0] = duped_symbol_name;
 804
 805    // Confirm byte length was calculated exactly correctly
 806    std.debug.assert(writer.end == bytes.len);
 807    return .{
 808        .bytes = bytes,
 809        .name = module_import_name,
 810        .symbol_names_for_import_lib = symbol_names_for_import_lib,
 811    };
 812}
 813
 814const WeakExternalOptions = struct {
 815    imp_prefix: bool,
 816    machine_type: std.coff.IMAGE.FILE.MACHINE,
 817};
 818
 819fn getWeakExternal(
 820    arena: std.mem.Allocator,
 821    module_import_name: []const u8,
 822    sym: []const u8,
 823    weak: []const u8,
 824    options: WeakExternalOptions,
 825) error{OutOfMemory}!Members.Member {
 826    const number_of_sections = 1;
 827    const number_of_symbols = 4;
 828    const number_of_weak_external_defs = 1;
 829    const pointer_to_symbol_table = @sizeOf(std.coff.Header) +
 830        (@sizeOf(std.coff.SectionHeader) * number_of_sections);
 831
 832    const symbol_names = try arena.alloc([]const u8, 2);
 833
 834    symbol_names[0] = if (options.imp_prefix)
 835        try std.mem.concat(arena, u8, &.{ "__imp_", sym })
 836    else
 837        try arena.dupe(u8, sym);
 838
 839    symbol_names[1] = if (options.imp_prefix)
 840        try std.mem.concat(arena, u8, &.{ "__imp_", weak })
 841    else
 842        try arena.dupe(u8, weak);
 843
 844    const string_table_byte_len = 4 + symbol_names[0].len + 1 + symbol_names[1].len + 1;
 845    const total_byte_len = pointer_to_symbol_table +
 846        (std.coff.Symbol.sizeOf() * number_of_symbols) +
 847        (std.coff.WeakExternalDefinition.sizeOf() * number_of_weak_external_defs) +
 848        string_table_byte_len;
 849
 850    const bytes = try arena.alloc(u8, total_byte_len);
 851    errdefer arena.free(bytes);
 852    var writer: std.Io.Writer = .fixed(bytes);
 853
 854    writer.writeStruct(std.coff.Header{
 855        .machine = options.machine_type,
 856        .number_of_sections = number_of_sections,
 857        .time_date_stamp = 0,
 858        .pointer_to_symbol_table = @intCast(pointer_to_symbol_table),
 859        .number_of_symbols = number_of_symbols + number_of_weak_external_defs,
 860        .size_of_optional_header = 0,
 861        .flags = .{},
 862    }, .little) catch unreachable;
 863
 864    writer.writeStruct(std.coff.SectionHeader{
 865        .name = ".drectve".*,
 866        .virtual_size = 0,
 867        .virtual_address = 0,
 868        .size_of_raw_data = 0,
 869        .pointer_to_raw_data = 0,
 870        .pointer_to_relocations = 0,
 871        .pointer_to_linenumbers = 0,
 872        .number_of_relocations = 0,
 873        .number_of_linenumbers = 0,
 874        .flags = .{
 875            .LNK_INFO = true,
 876            .LNK_REMOVE = true,
 877        },
 878    }, .little) catch unreachable;
 879
 880    writeSymbol(&writer, .{
 881        .name = "@comp.id".*,
 882        .value = 0,
 883        .section_number = .ABSOLUTE,
 884        .type = .{
 885            .base_type = .NULL,
 886            .complex_type = .NULL,
 887        },
 888        .storage_class = .STATIC,
 889        .number_of_aux_symbols = 0,
 890    }) catch unreachable;
 891    writeSymbol(&writer, .{
 892        .name = "@feat.00".*,
 893        .value = 0,
 894        .section_number = .ABSOLUTE,
 895        .type = .{
 896            .base_type = .NULL,
 897            .complex_type = .NULL,
 898        },
 899        .storage_class = .STATIC,
 900        .number_of_aux_symbols = 0,
 901    }) catch unreachable;
 902    var string_table_offset: usize = first_string_table_entry_offset;
 903    writeSymbol(&writer, .{
 904        .name = first_string_table_entry,
 905        .value = 0,
 906        .section_number = @enumFromInt(0),
 907        .type = .{
 908            .base_type = .NULL,
 909            .complex_type = .NULL,
 910        },
 911        .storage_class = .EXTERNAL,
 912        .number_of_aux_symbols = 0,
 913    }) catch unreachable;
 914    string_table_offset += symbol_names[0].len + 1;
 915    writeSymbol(&writer, .{
 916        .name = getNameBytesForStringTableOffset(@intCast(string_table_offset)),
 917        .value = 0,
 918        .section_number = @enumFromInt(0),
 919        .type = .{
 920            .base_type = .NULL,
 921            .complex_type = .NULL,
 922        },
 923        .storage_class = .WEAK_EXTERNAL,
 924        .number_of_aux_symbols = 1,
 925    }) catch unreachable;
 926    writeWeakExternalDefinition(&writer, .{
 927        .tag_index = 2,
 928        .flag = .SEARCH_ALIAS,
 929        .unused = @splat(0),
 930    }) catch unreachable;
 931
 932    // string table
 933    writer.writeInt(u32, @intCast(string_table_byte_len), .little) catch unreachable;
 934    writer.writeAll(symbol_names[0]) catch unreachable;
 935    writer.writeByte(0) catch unreachable;
 936    writer.writeAll(symbol_names[1]) catch unreachable;
 937    writer.writeByte(0) catch unreachable;
 938
 939    // Confirm byte length was calculated exactly correctly
 940    std.debug.assert(writer.end == bytes.len);
 941    return .{
 942        .bytes = bytes,
 943        .name = module_import_name,
 944        .symbol_names_for_import_lib = symbol_names,
 945    };
 946}
 947
 948const GetShortImportError = error{UnknownImportType} || std.mem.Allocator.Error;
 949
 950fn getShortImport(
 951    arena: std.mem.Allocator,
 952    module_import_name: []const u8,
 953    sym: []const u8,
 954    export_name: ?[]const u8,
 955    machine_type: std.coff.IMAGE.FILE.MACHINE,
 956    ordinal_hint: u16,
 957    import_type: std.coff.ImportType,
 958    name_type: std.coff.ImportNameType,
 959) GetShortImportError!Members.Member {
 960    var size_of_data = module_import_name.len + 1 + sym.len + 1;
 961    if (export_name) |name| size_of_data += name.len + 1;
 962    const total_byte_len = @sizeOf(std.coff.ImportHeader) + size_of_data;
 963
 964    const bytes = try arena.alloc(u8, total_byte_len);
 965    errdefer arena.free(bytes);
 966    var writer = std.Io.Writer.fixed(bytes);
 967
 968    writer.writeStruct(std.coff.ImportHeader{
 969        .version = 0,
 970        .machine = machine_type,
 971        .time_date_stamp = 0,
 972        .size_of_data = @intCast(size_of_data),
 973        .hint = ordinal_hint,
 974        .types = .{
 975            .type = import_type,
 976            .name_type = name_type,
 977            .reserved = 0,
 978        },
 979    }, .little) catch unreachable;
 980
 981    writer.writeAll(sym) catch unreachable;
 982    writer.writeByte(0) catch unreachable;
 983    writer.writeAll(module_import_name) catch unreachable;
 984    writer.writeByte(0) catch unreachable;
 985    if (export_name) |name| {
 986        writer.writeAll(name) catch unreachable;
 987        writer.writeByte(0) catch unreachable;
 988    }
 989
 990    var symbol_names_for_import_lib: std.ArrayList([]const u8) = try .initCapacity(arena, 2);
 991
 992    switch (import_type) {
 993        .CODE, .CONST => {
 994            symbol_names_for_import_lib.appendAssumeCapacity(try std.mem.concat(arena, u8, &.{ "__imp_", sym }));
 995            symbol_names_for_import_lib.appendAssumeCapacity(try arena.dupe(u8, sym));
 996        },
 997        .DATA => {
 998            symbol_names_for_import_lib.appendAssumeCapacity(try std.mem.concat(arena, u8, &.{ "__imp_", sym }));
 999        },
1000        else => return error.UnknownImportType,
1001    }
1002
1003    // Confirm byte length was calculated exactly correctly
1004    std.debug.assert(writer.end == bytes.len);
1005    return .{
1006        .bytes = bytes,
1007        .name = module_import_name,
1008        .symbol_names_for_import_lib = try symbol_names_for_import_lib.toOwnedSlice(arena),
1009    };
1010}
1011
1012fn writeSymbol(writer: *std.Io.Writer, symbol: std.coff.Symbol) !void {
1013    try writer.writeAll(&symbol.name);
1014    try writer.writeInt(u32, symbol.value, .little);
1015    try writer.writeInt(u16, @intFromEnum(symbol.section_number), .little);
1016    try writer.writeInt(u8, @intFromEnum(symbol.type.base_type), .little);
1017    try writer.writeInt(u8, @intFromEnum(symbol.type.complex_type), .little);
1018    try writer.writeInt(u8, @intFromEnum(symbol.storage_class), .little);
1019    try writer.writeInt(u8, symbol.number_of_aux_symbols, .little);
1020}
1021
1022fn writeWeakExternalDefinition(writer: *std.Io.Writer, weak_external: std.coff.WeakExternalDefinition) !void {
1023    try writer.writeInt(u32, weak_external.tag_index, .little);
1024    try writer.writeInt(u32, @intFromEnum(weak_external.flag), .little);
1025    try writer.writeAll(&weak_external.unused);
1026}
1027
1028fn writeRelocation(writer: *std.Io.Writer, relocation: std.coff.Relocation) !void {
1029    try writer.writeInt(u32, relocation.virtual_address, .little);
1030    try writer.writeInt(u32, relocation.symbol_table_index, .little);
1031    try writer.writeInt(u16, relocation.type, .little);
1032}
1033
1034// https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#type-indicators
1035pub fn rvaRelocationTypeIndicator(target: std.coff.IMAGE.FILE.MACHINE) ?u16 {
1036    return switch (target) {
1037        .AMD64 => @intFromEnum(std.coff.IMAGE.REL.AMD64.ADDR32NB),
1038        .I386 => @intFromEnum(std.coff.IMAGE.REL.I386.DIR32NB),
1039        .ARMNT => @intFromEnum(std.coff.IMAGE.REL.ARM.ADDR32NB),
1040        .ARM64, .ARM64EC, .ARM64X => @intFromEnum(std.coff.IMAGE.REL.ARM64.ADDR32NB),
1041        .IA64 => @intFromEnum(std.coff.IMAGE.REL.IA64.DIR32NB),
1042        else => null,
1043    };
1044}
1045
1046const StringTable = struct {
1047    data: std.ArrayList(u8) = .empty,
1048    map: std.HashMapUnmanaged(u32, void, std.hash_map.StringIndexContext, std.hash_map.default_max_load_percentage) = .empty,
1049
1050    pub fn deinit(self: *StringTable, allocator: Allocator) void {
1051        self.data.deinit(allocator);
1052        self.map.deinit(allocator);
1053    }
1054
1055    pub fn put(self: *StringTable, allocator: Allocator, value: []const u8) !u32 {
1056        const result = try self.map.getOrPutContextAdapted(
1057            allocator,
1058            value,
1059            std.hash_map.StringIndexAdapter{ .bytes = &self.data },
1060            .{ .bytes = &self.data },
1061        );
1062        if (result.found_existing) {
1063            return result.key_ptr.*;
1064        }
1065
1066        try self.data.ensureUnusedCapacity(allocator, value.len + 1);
1067        const offset: u32 = @intCast(self.data.items.len);
1068
1069        self.data.appendSliceAssumeCapacity(value);
1070        self.data.appendAssumeCapacity(0);
1071
1072        result.key_ptr.* = offset;
1073
1074        return offset;
1075    }
1076
1077    pub fn get(self: StringTable, offset: u32) []const u8 {
1078        std.debug.assert(offset < self.data.items.len);
1079        return std.mem.sliceTo(@as([*:0]const u8, @ptrCast(self.data.items.ptr + offset)), 0);
1080    }
1081
1082    pub fn getOffset(self: *StringTable, value: []const u8) ?u32 {
1083        return self.map.getKeyAdapted(
1084            value,
1085            std.hash_map.StringIndexAdapter{ .bytes = &self.data },
1086        );
1087    }
1088};