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};