authorgravatar for kcbanner@gmail.comCasey Banner <kcbanner@gmail.com> 2026-06-05 01:55:33-04:00
committergravatar for kcbanner@gmail.comCasey Banner <kcbanner@gmail.com> 2026-06-23 00:21:00-04:00
logc8328cb57b9b23c474ab0c55ca5caa0e90bc5f80
tree2e7b3171e2e24aa27f06127f29b0c608da3e9814
parentc5d6277ace183cf4261c13cc9b44a53ec1622199

Coff: Implement writing the export table


2 files changed, 347 insertions(+), 10 deletions(-)

lib/std/coff.zig+44
......@@ -452,6 +452,50 @@ pub const ImportHintNameEntry = extern struct {
452452 name: [1]u8,
453453};
454454
455pub const ExportDirectoryTable = extern struct {
456 /// Reserved
457 flags: u32,
458
459 /// Creation time of this table
460 time_date_stamp: u32,
461
462 major_version: u16,
463 minor_version: u16,
464
465 /// The address of an ASCII string that contains the name of the DLL.
466 /// This address is relative to the image base.
467 name_rva: u32,
468
469 /// The ordinal of the first export in this image
470 ordinal_base: u32,
471
472 /// Number of entries in the export address table
473 number_of_entries: u32,
474
475 /// Number of entries in the name pointer table and ordinal table
476 number_of_names: u32,
477
478 export_address_table_rva: u32,
479 name_pointer_table_rva: u32,
480 ordinal_table_rva: u32,
481};
482
483pub const ExportAddressTableEntry = extern struct {
484 /// If this address is within the export section, then this is the address of the export
485 /// Otherwise, this is the address of a string that specfies a symbol in another DLL:
486 /// <dll name>.<export name>
487 /// <dll name>.#<export ordinal>
488 export_or_forwarder_rva: u32,
489};
490
491pub const ExportNamePointerTableEntry = extern struct {
492 name_rva: u32,
493};
494
495pub const ExportOrdinalTableEntry = extern struct {
496 unbiased_ordinal: u16,
497};
498
455499pub const SectionHeader = extern struct {
456500 name: [8]u8,
457501 virtual_size: u32,
src/link/Coff.zig+303-10
......@@ -22,6 +22,7 @@ base: link.File,
2222mf: MappedFile,
2323nodes: std.MultiArrayList(Node),
2424import_table: ImportTable,
25export_table: ExportTable,
2526strings: std.HashMapUnmanaged(
2627 u32,
2728 void,
......@@ -146,6 +147,12 @@ pub const Node = union(enum) {
146147 import_address_table: ImportTable.Index,
147148 import_hint_name_table: ImportTable.Index,
148149
150 export_directory_table,
151 export_address_table,
152 export_name_pointer_table,
153 export_ordinal_table,
154 export_name_table,
155
149156 pseudo_section: PseudoSectionMapIndex,
150157 object_section: ObjectSectionMapIndex,
151158 global: GlobalMapIndex,
......@@ -270,6 +277,44 @@ pub const Node = union(enum) {
270277 }
271278};
272279
280pub const ExportTable = struct {
281 ni: MappedFile.Node.Index,
282 export_address_table_ni: MappedFile.Node.Index,
283 name_pointer_table_ni: MappedFile.Node.Index,
284 ordinal_table_ni: MappedFile.Node.Index,
285 name_table_ni: MappedFile.Node.Index,
286 entries: std.AutoArrayHashMapUnmanaged(void, Entry),
287
288 pub const Entry = struct {
289 name_index: u32,
290 name_len: u32,
291 };
292
293 const Adapter = struct {
294 coff: *Coff,
295
296 pub fn eql(adapter: Adapter, lhs_key: []const u8, _: void, rhs_index: usize) bool {
297 const coff = adapter.coff;
298 const name_table_slice = coff.export_table.name_table_ni.slice(&coff.mf);
299 const rhs = coff.export_table.entries.values()[rhs_index];
300 return std.mem.eql(u8, name_table_slice[rhs.name_index..][0..rhs.name_len], lhs_key);
301 }
302
303 pub fn hash(_: Adapter, key: []const u8) u32 {
304 assert(std.mem.indexOfScalar(u8, key, 0) == null);
305 return std.array_hash_map.hashString(key);
306 }
307 };
308
309 pub const Index = enum(u32) {
310 _,
311
312 pub fn get(export_index: ExportTable.Index, coff: *Coff) *Entry {
313 return &coff.export_table.entries.values()[@intFromEnum(export_index)];
314 }
315 };
316};
317
273318pub const ImportTable = struct {
274319 ni: MappedFile.Node.Index,
275320 entries: std.array_hash_map.Auto(void, Entry),
......@@ -314,6 +359,7 @@ pub const String = enum(u32) {
314359 @".rdata" = 13,
315360 @".text" = 20,
316361 @".tls$" = 26,
362 @".edata" = 32,
317363 _,
318364
319365 pub const Optional = enum(u32) {
......@@ -321,6 +367,7 @@ pub const String = enum(u32) {
321367 @".rdata" = @intFromEnum(String.@".rdata"),
322368 @".text" = @intFromEnum(String.@".text"),
323369 @".tls$" = @intFromEnum(String.@".tls$"),
370 @".edata" = @intFromEnum(String.@".edata"),
324371 none = std.math.maxInt(u32),
325372 _,
326373
......@@ -695,6 +742,14 @@ fn create(
695742 .ni = .none,
696743 .entries = .empty,
697744 },
745 .export_table = .{
746 .ni = .none,
747 .export_address_table_ni = .none,
748 .name_pointer_table_ni = .none,
749 .ordinal_table_ni = .none,
750 .name_table_ni = .none,
751 .entries = .empty,
752 },
698753 .strings = .empty,
699754 .string_bytes = .empty,
700755 .image_section_table = .empty,
......@@ -741,6 +796,7 @@ pub fn deinit(coff: *Coff) void {
741796 coff.mf.deinit(gpa);
742797 coff.nodes.deinit(gpa);
743798 coff.import_table.entries.deinit(gpa);
799 coff.export_table.entries.deinit(gpa);
744800 coff.strings.deinit(gpa);
745801 coff.string_bytes.deinit(gpa);
746802 coff.image_section_table.deinit(gpa);
......@@ -780,7 +836,7 @@ fn initHeaders(
780836 else
781837 0;
782838
783 const expected_nodes_len = Node.known_count + 6 +
839 const expected_nodes_len = Node.known_count + 12 +
784840 @as(usize, @intFromBool(comp.config.any_non_single_threaded)) * 2;
785841 try coff.nodes.ensureTotalCapacity(gpa, expected_nodes_len);
786842 coff.nodes.appendAssumeCapacity(.file);
......@@ -1005,6 +1061,67 @@ fn initHeaders(
10051061 );
10061062 coff.nodes.appendAssumeCapacity(.import_directory_table);
10071063
1064 {
1065 // TODO: Could create this lazily when processing the first export instead?
1066 const edata_section_ni = (try coff.pseudoSectionMapIndex(
1067 .@".edata",
1068 .of(std.coff.ExportDirectoryTable),
1069 .{ .read = true },
1070 )).symbol(coff).node(coff);
1071
1072 const name = "TODO_NAME.dll";
1073 const name_index = @sizeOf(std.coff.ExportDirectoryTable);
1074 coff.export_table.ni = try coff.mf.addLastChildNode(
1075 gpa,
1076 edata_section_ni,
1077 .{
1078 .size = @sizeOf(std.coff.ExportDirectoryTable) + name.len + 1,
1079 .alignment = .of(std.coff.ExportDirectoryTable),
1080 .fixed = true,
1081 .moved = true,
1082 },
1083 );
1084 @memcpy(coff.export_table.ni.slice(&coff.mf)[name_index..][0 .. name.len + 1], name[0 .. name.len + 1]);
1085
1086 coff.export_table.export_address_table_ni = try coff.mf.addLastChildNode(gpa, edata_section_ni, .{
1087 .alignment = .of(u32),
1088 .moved = true,
1089 });
1090 coff.export_table.name_pointer_table_ni = try coff.mf.addLastChildNode(gpa, edata_section_ni, .{
1091 .alignment = .of(u32),
1092 .moved = true,
1093 });
1094 coff.export_table.ordinal_table_ni = try coff.mf.addLastChildNode(gpa, edata_section_ni, .{
1095 .alignment = .of(u16),
1096 .moved = true,
1097 });
1098 coff.export_table.name_table_ni = try coff.mf.addLastChildNode(gpa, edata_section_ni, .{
1099 .alignment = .of(u8),
1100 .moved = true,
1101 });
1102
1103 coff.nodes.appendAssumeCapacity(.export_directory_table);
1104 coff.nodes.appendAssumeCapacity(.export_address_table);
1105 coff.nodes.appendAssumeCapacity(.export_name_pointer_table);
1106 coff.nodes.appendAssumeCapacity(.export_ordinal_table);
1107 coff.nodes.appendAssumeCapacity(.export_name_table);
1108
1109 const export_directory_table = coff.exportDirectoryTable();
1110 export_directory_table.* = .{
1111 .flags = 0,
1112 .time_date_stamp = timestamp,
1113 .major_version = 0,
1114 .minor_version = 0,
1115 .name_rva = 0,
1116 .ordinal_base = 1,
1117 .number_of_entries = 0,
1118 .number_of_names = 0,
1119 .export_address_table_rva = 0,
1120 .name_pointer_table_rva = 0,
1121 .ordinal_table_rva = 0,
1122 };
1123 }
1124
10081125 // While tls variables allocated at runtime are writable, the template itself is not
10091126 if (comp.config.any_non_single_threaded) _ = try coff.objectSectionMapIndex(
10101127 .@".tls$",
......@@ -1048,6 +1165,7 @@ fn computeNodeRva(coff: *Coff, ni: MappedFile.Node.Index) u32 {
10481165 .optional_header,
10491166 .data_directories,
10501167 .section_table,
1168 .export_name_table,
10511169 => unreachable,
10521170 .image_section => |si| si,
10531171 .import_directory_table => break :parent_rva coff.targetLoad(
......@@ -1062,6 +1180,18 @@ fn computeNodeRva(coff: *Coff, ni: MappedFile.Node.Index) u32 {
10621180 .import_hint_name_table => |import_index| break :parent_rva coff.targetLoad(
10631181 &coff.importDirectoryEntryPtr(import_index).name_rva,
10641182 ),
1183 .export_directory_table => break :parent_rva coff.targetLoad(
1184 &coff.dataDirectoryPtr(.EXPORT).virtual_address,
1185 ),
1186 .export_address_table => break :parent_rva coff.targetLoad(
1187 &coff.exportDirectoryTable().export_address_table_rva,
1188 ),
1189 .export_name_pointer_table => break :parent_rva coff.targetLoad(
1190 &coff.exportDirectoryTable().name_pointer_table_rva,
1191 ),
1192 .export_ordinal_table => break :parent_rva coff.targetLoad(
1193 &coff.exportDirectoryTable().ordinal_table_rva,
1194 ),
10651195 inline .pseudo_section,
10661196 .object_section,
10671197 .global,
......@@ -1181,6 +1311,22 @@ pub fn importDirectoryEntryPtr(
11811311 return &coff.importDirectoryTableSlice()[@intFromEnum(import_index)];
11821312}
11831313
1314pub fn exportDirectoryTable(coff: *Coff) *std.coff.ExportDirectoryTable {
1315 return @ptrCast(@alignCast(coff.export_table.ni.slice(&coff.mf)));
1316}
1317
1318pub fn exportAddressTableSlice(coff: *Coff) []std.coff.ExportAddressTableEntry {
1319 return @ptrCast(@alignCast(coff.export_table.export_address_table_ni.slice(&coff.mf)));
1320}
1321
1322pub fn exportNamePointerTableSlice(coff: *Coff) []std.coff.ExportNamePointerTableEntry {
1323 return @ptrCast(@alignCast(coff.export_table.name_pointer_table_ni.slice(&coff.mf)));
1324}
1325
1326pub fn exportOrdinalTableSlice(coff: *Coff) []std.coff.ExportOrdinalTableEntry {
1327 return @ptrCast(@alignCast(coff.export_table.ordinal_table_ni.slice(&coff.mf)));
1328}
1329
11841330fn addSymbolAssumeCapacity(coff: *Coff) Symbol.Index {
11851331 defer coff.symbol_table.addOneAssumeCapacity().* = .{
11861332 .ni = .none,
......@@ -1729,6 +1875,82 @@ pub fn updateErrorData(coff: *Coff, pt: Zcu.PerThread) !void {
17291875 };
17301876}
17311877
1878fn flushExports(coff: *Coff, tid: Zcu.PerThread.Id) !void {
1879 const export_count = coff.export_table.entries.count();
1880 if (export_count == 0) return;
1881
1882 const gpa = coff.base.comp.zcu.?.gpa;
1883 const edt = coff.exportDirectoryTable();
1884 edt.number_of_names = @intCast(export_count);
1885 edt.number_of_entries = @intCast(export_count);
1886
1887 try coff.export_table.name_pointer_table_ni.resize(
1888 &coff.mf,
1889 gpa,
1890 export_count * @sizeOf(std.coff.ExportNamePointerTableEntry),
1891 );
1892
1893 try coff.export_table.ordinal_table_ni.resize(
1894 &coff.mf,
1895 gpa,
1896 export_count * @sizeOf(std.coff.ExportOrdinalTableEntry),
1897 );
1898
1899 while (try coff.idle(tid)) {}
1900 if (coff.targetEndian() != native_endian)
1901 std.mem.byteSwapAllFields(std.coff.ExportDirectoryTable, edt);
1902
1903 const name_table_rva = coff.computeNodeRva(coff.export_table.name_table_ni);
1904 for (
1905 coff.exportNamePointerTableSlice(),
1906 coff.exportOrdinalTableSlice(),
1907 coff.export_table.entries.values(),
1908 0..,
1909 ) |*np, *ord, entry, entry_i| {
1910 np.name_rva = name_table_rva + entry.name_index;
1911 if (coff.targetEndian() != native_endian)
1912 std.mem.byteSwapAllFields(std.coff.ExportNamePointerTableEntry, np);
1913
1914 ord.unbiased_ordinal = @intCast(entry_i);
1915 if (coff.targetEndian() != native_endian)
1916 std.mem.byteSwapAllFields(std.coff.ExportOrdinalTableEntry, &ord);
1917 }
1918
1919 const Context = struct {
1920 np: []std.coff.ExportNamePointerTableEntry,
1921 ord: []std.coff.ExportOrdinalTableEntry,
1922 entries: []ExportTable.Entry,
1923 names: []const u8,
1924
1925 pub fn lessThan(ctx: @This(), lhs: usize, rhs: usize) bool {
1926 const lhs_entry = &ctx.entries[lhs];
1927 const rhs_entry = &ctx.entries[rhs];
1928 return std.mem.lessThan(
1929 u8,
1930 ctx.names[lhs_entry.name_index..][0..lhs_entry.name_len],
1931 ctx.names[rhs_entry.name_index..][0..rhs_entry.name_len],
1932 );
1933 }
1934
1935 pub fn swap(ctx: @This(), lhs: usize, rhs: usize) void {
1936 std.mem.swap(std.coff.ExportNamePointerTableEntry, &ctx.np[lhs], &ctx.np[rhs]);
1937 std.mem.swap(std.coff.ExportOrdinalTableEntry, &ctx.ord[lhs], &ctx.ord[rhs]);
1938 std.mem.swap(ExportTable.Entry, &ctx.entries[lhs], &ctx.entries[rhs]);
1939 }
1940 };
1941
1942 std.sort.pdqContext(0, export_count, Context{
1943 .np = coff.exportNamePointerTableSlice(),
1944 .ord = coff.exportOrdinalTableSlice(),
1945 .entries = coff.export_table.entries.values(),
1946 .names = coff.export_table.name_table_ni.slice(&coff.mf),
1947 });
1948
1949 // TODO: Is there a way to know if this is the last flush? We could skip doing this if so.
1950 // TODO: Need to reindex with adaptor?
1951 //try coff.export_table.entries.reIndexContext(gpa, ExportTable.Adapter{ .coff = coff });
1952}
1953
17321954pub fn flush(
17331955 coff: *Coff,
17341956 arena: std.mem.Allocator,
......@@ -1739,6 +1961,9 @@ pub fn flush(
17391961 _ = prog_node;
17401962 while (try coff.idle(tid)) {}
17411963
1964 coff.flushExports(tid) catch |err|
1965 return coff.base.comp.link_diags.fail("linker failed to flush exports: {t}", .{err});
1966
17421967 // hack for stage2_x86_64 + coff
17431968 const comp = coff.base.comp;
17441969 if (comp.compiler_rt_dyn_lib) |crt_file| {
......@@ -1932,19 +2157,20 @@ fn flushGlobal(coff: *Coff, pt: Zcu.PerThread, gmi: Node.GlobalMapIndex) !void {
19322157 const comp = zcu.comp;
19332158 const gpa = zcu.gpa;
19342159 const gn = gmi.globalName(coff);
2160
2161 const target_endian = coff.targetEndian();
2162 const magic = coff.targetLoad(&coff.optionalHeaderStandardPtr().magic);
2163 const addr_size: u64, const addr_align: std.mem.Alignment = switch (magic) {
2164 _ => unreachable,
2165 .PE32 => .{ 4, .@"4" },
2166 .@"PE32+" => .{ 8, .@"8" },
2167 };
2168
2169 const name = gn.name.toSlice(coff);
19352170 if (gn.lib_name.toSlice(coff)) |lib_name| {
1936 const name = gn.name.toSlice(coff);
19372171 try coff.nodes.ensureUnusedCapacity(gpa, 4);
19382172 try coff.symbol_table.ensureUnusedCapacity(gpa, 1);
19392173
1940 const target_endian = coff.targetEndian();
1941 const magic = coff.targetLoad(&coff.optionalHeaderStandardPtr().magic);
1942 const addr_size: u64, const addr_align: std.mem.Alignment = switch (magic) {
1943 _ => unreachable,
1944 .PE32 => .{ 4, .@"4" },
1945 .@"PE32+" => .{ 8, .@"8" },
1946 };
1947
19482174 const gop = try coff.import_table.entries.getOrPutAdapted(
19492175 gpa,
19502176 lib_name,
......@@ -2089,6 +2315,47 @@ fn flushGlobal(coff: *Coff, pt: Zcu.PerThread, gmi: Node.GlobalMapIndex) !void {
20892315 coff.nodes.appendAssumeCapacity(.{ .global = gmi });
20902316 sym.rva = coff.computeNodeRva(sym.ni);
20912317 si.applyLocationRelocs(coff);
2318 } else {
2319 const entries_ctx = ExportTable.Adapter{ .coff = coff };
2320 const gop = try coff.export_table.entries.getOrPutAdapted(
2321 gpa,
2322 name,
2323 entries_ctx,
2324 );
2325
2326 if (!gop.found_existing) {
2327 errdefer _ = coff.export_table.entries.pop();
2328 if (coff.export_table.entries.count() > std.math.maxInt(@FieldType(std.coff.ExportDirectoryTable, "number_of_entries")))
2329 return coff.base.comp.link_diags.fail("exceeded maximum number of exports", .{});
2330
2331 const name_index = coff.export_table.name_table_ni.fileLocation(&coff.mf, true).size;
2332 const new_name_table_size = name_index + name.len + 1;
2333 if (new_name_table_size > std.math.maxInt(@FieldType(ExportTable.Entry, "name_index")))
2334 return coff.base.comp.link_diags.fail("exports name table limit reached", .{});
2335
2336 try coff.export_table.name_table_ni.resize(&coff.mf, gpa, new_name_table_size);
2337
2338 const name_table_slice = coff.export_table.name_table_ni.slice(&coff.mf);
2339 @memcpy(name_table_slice[name_index..][0 .. name.len + 1], name[0 .. name.len + 1]);
2340
2341 gop.value_ptr.* = .{
2342 .name_index = @intCast(name_index),
2343 .name_len = @intCast(name.len),
2344 };
2345
2346 const si = gmi.symbol(coff);
2347 const sym = si.get(coff);
2348
2349 try coff.export_table.export_address_table_ni.resize(
2350 &coff.mf,
2351 gpa,
2352 coff.export_table.entries.count() * @sizeOf(std.coff.ExportAddressTableEntry),
2353 );
2354 const ea = &coff.exportAddressTableSlice()[gop.index];
2355 ea.export_or_forwarder_rva = sym.rva;
2356 if (coff.targetEndian() != native_endian)
2357 std.mem.byteSwapAllFields(std.coff.ExportAddressTableEntry, &ea);
2358 }
20922359 }
20932360}
20942361
......@@ -2218,6 +2485,27 @@ fn flushMoved(coff: *Coff, ni: MappedFile.Node.Index) !void {
22182485 import_hint_name_index += 2;
22192486 }
22202487 },
2488 .export_directory_table => {
2489 const rva = coff.computeNodeRva(ni);
2490 coff.targetStore(&coff.dataDirectoryPtr(.EXPORT).virtual_address, rva);
2491 coff.targetStore(&coff.exportDirectoryTable().name_rva, rva + @sizeOf(std.coff.ExportDirectoryTable));
2492 },
2493 .export_address_table => coff.targetStore(
2494 &coff.exportDirectoryTable().export_address_table_rva,
2495 coff.computeNodeRva(ni),
2496 ),
2497 .export_name_pointer_table => coff.targetStore(
2498 &coff.exportDirectoryTable().name_pointer_table_rva,
2499 coff.computeNodeRva(ni),
2500 ),
2501 .export_ordinal_table => coff.targetStore(
2502 &coff.exportDirectoryTable().ordinal_table_rva,
2503 coff.computeNodeRva(ni),
2504 ),
2505 .export_name_table => {
2506 // .export_name_pointer_table entries are updated in flush
2507 log.warn("flushMoved export_name_table unhandled", .{});
2508 },
22212509 inline .pseudo_section,
22222510 .object_section,
22232511 .global,
......@@ -2272,6 +2560,11 @@ fn flushResized(coff: *Coff, ni: MappedFile.Node.Index) !void {
22722560 @intCast(size),
22732561 ),
22742562 .import_lookup_table, .import_address_table, .import_hint_name_table => {},
2563 .export_directory_table => coff.targetStore(
2564 &coff.dataDirectoryPtr(.EXPORT).size,
2565 @intCast(size),
2566 ),
2567 .export_address_table, .export_name_pointer_table, .export_ordinal_table, .export_name_table => {},
22752568 inline .pseudo_section,
22762569 .object_section,
22772570 => |smi| smi.symbol(coff).get(coff).size = @intCast(size),