zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

commit 1b95427715b58cf4cd6df63fc9a7a8d200ca5d47 (tree)
parent 72bc140ad16a4a2dbe632129ea9068630e019277
Author: kcbanner <kcbanner@gmail.com>
Date:   Fri,  5 Jun 2026 01:55:37 -0400

Coff: add support for .is_dll_import

x86_64: emit the correct encodings for loading / calling .is_dll_import externs
Coff: fix emitting an empty import directory
objdump: add --exports=sort for sorting implibs in snapshots (self-hosted outputs members in a different order than llvm)
objdump: fix some missed redactions / filters / elements calls
test: add dllimport standalone test

Diffstat:
Mlib/compiler/objdump.zig | 102++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Msrc/codegen/x86_64/Emit.zig | 30++++++++++++++++++++++++++++--
Msrc/link/Coff.zig | 27++++++++++++++++++---------
Mtest/link.zig | 33+++++++++++++++++++++------------
Atest/link/snapshots/abs-symbol.x86_64.dmp | 1+
Mtest/link/snapshots/dynamic-lib-code.implib-windows.dmp | 4++--
Atest/link/snapshots/dynamic-lib-code.implib-x86_64-windows.dmp | 32++++++++++++++++++++++++++++++++
Atest/link/snapshots/dynamic-lib-data.dmp | 14++++++++++++++
Atest/link/snapshots/dynamic-lib-data.implib-x86_64-windows.dmp | 41+++++++++++++++++++++++++++++++++++++++++
Mtest/src/Link.zig | 3++-
Mtest/standalone/shared_library/mathtest.zig | 2++
Mtest/standalone/shared_library/test.c | 9+++++++++
12 files changed, 250 insertions(+), 48 deletions(-)

diff --git a/lib/compiler/objdump.zig b/lib/compiler/objdump.zig @@ -11,6 +11,7 @@ var stdout_buffer: [4000]u8 = undefined; const Options = struct { exports: bool, + exports_sort: bool, file_headers: bool, imports: bool, input_path: []const u8, @@ -53,6 +54,7 @@ pub fn main(init: std.process.Init) !void { var i: usize = 1; var opt_exports: ?bool = null; + var opt_exports_sort: ?bool = null; var opt_file_headers: ?bool = null; var opt_imports: ?bool = null; var opt_input_path: ?[]const u8 = null; @@ -81,9 +83,11 @@ pub fn main(init: std.process.Init) !void { opt_section_headers = true; opt_symbols = true; opt_relocs = true; - } else if (mem.eql(u8, arg, "--exports")) { + } else if (mem.startsWith(u8, arg, "--exports")) { opt_exports = true; opt_linker_member = .second_linker; + if (mem.eql(u8, arg["--exports".len..], "=sort")) + opt_exports_sort = true; } else if (mem.eql(u8, arg, "--file-headers")) { opt_file_headers = true; } else if (mem.eql(u8, arg, "--imports")) { @@ -156,6 +160,7 @@ pub fn main(init: std.process.Init) !void { const opts: Options = .{ .input_path = opt_input_path orelse fatal("missing input file path positional argument", .{}), .exports = opt_exports orelse false, + .exports_sort = opt_exports_sort orelse false, .file_headers = opt_file_headers orelse false, .imports = opt_imports orelse false, .linker_member = opt_linker_member, @@ -355,9 +360,12 @@ const coff = struct { const r = &fr.interface; r.toss(std.coff.archive_signature.len); - var members: std.ArrayList(struct { + const Member = struct { offset: u32, - }) = .empty; + order: ?u32, + }; + + var members: std.ArrayList(Member) = .empty; defer members.deinit(gpa); var symbol_member_indices: std.ArrayList(u32) = .empty; defer symbol_member_indices.deinit(gpa); @@ -415,6 +423,10 @@ const coff = struct { for (0..num_symbols) |symbol_i| { const symbol = r.takeDelimiter(0) catch |err| return d.failParse("unable to read first linker member string table: {t}", .{err}); + + if (!filterMatches(d.opts.symbol_filters, symbol.?)) + continue; + const offset = std.mem.readInt(u32, offsets[symbol_i * 4 ..][0..4], .big); try w.print("{f} {s}\n", .{ fmtIntField(d, offset, .{ .kind = .va }), @@ -441,6 +453,7 @@ const coff = struct { for (0..num_members) |_| members.addOneAssumeCapacity().* = .{ .offset = try r.takeInt(u32, .little), + .order = null, }; const num_symbols = try r.takeInt(u32, .little); @@ -451,14 +464,42 @@ const coff = struct { if (dump_header) try w.print( \\{t: >16} type - \\ | {d} symbols - \\ | {d} members + \\ | {f} symbols + \\ | {f} members \\ - , .{ expected_kind, num_symbols, num_members }); + , .{ + expected_kind, + fmtIntField(d, num_symbols, .{ .kind = .size, .width = .auto }), + fmtIntField(d, num_members, .{ .kind = .size, .width = .auto }), + }); try symbol_member_indices.ensureTotalCapacity(gpa, num_symbols); - for (0..num_symbols) |_| - symbol_member_indices.addOneAssumeCapacity().* = (try r.takeInt(u16, .little)) - 1; + for (0..num_symbols) |order| { + const index = (try r.takeInt(u16, .little)) - 1; + if (index >= members.items.len) + return d.failParse("invalid member index 0x{x} in seconds linker member indices array", .{index}); + + symbol_member_indices.addOneAssumeCapacity().* = index; + + if (members.items[index].order == null) + members.items[index].order = @intCast(order); + } + + if (d.opts.exports and d.opts.exports_sort) { + std.sort.pdq(Member, members.items, {}, struct { + fn lessThan(ctx: void, lhs: Member, rhs: Member) bool { + _ = ctx; + if (lhs.order == null and rhs.order == null) + return lhs.offset < rhs.offset + else if (lhs.order) |lhs_order| + return if (rhs.order) |rhs_order| lhs_order < rhs_order else false + else if (rhs.order) |rhs_order| + return if (lhs.order) |lhs_order| lhs_order < rhs_order else true + else + unreachable; + } + }.lessThan); + } if (d.opts.linker_member == .second_linker) { if (d.element(.@"table-header")) @@ -480,6 +521,9 @@ const coff = struct { else => |e| return e, }) |n| n else return d.failParse("unterminated string found in second linker member", .{}); + if (!filterMatches(d.opts.symbol_filters, symbol_name)) + continue; + try w.print("{f} {s}\n", .{ fmtIntField( d, @@ -844,8 +888,8 @@ const coff = struct { section_i + 1, raw_name, fmtIntField(d, section.header.virtual_address, .{ .kind = .va }), - fmtIntField(d, section.header.virtual_size, .{ .kind = .size, .width = 9 }), - fmtIntField(d, section.header.size_of_raw_data, .{ .kind = .size, .width = 9 }), + fmtIntField(d, section.header.virtual_size, .{ .kind = .size, .width = .{ .explicit = 9 } }), + fmtIntField(d, section.header.size_of_raw_data, .{ .kind = .size, .width = .{ .explicit = 9 } }), fmtIntField(d, section.header.pointer_to_raw_data, .{ .kind = .va }), fmtIntField(d, section.header.pointer_to_relocations, .{ .kind = .va }), fmtIntField(d, section.header.pointer_to_linenumbers, .{ .kind = .va }), @@ -1309,14 +1353,16 @@ const coff = struct { const dll_name = (try r.takeDelimiter(0)).?; - try w.print("Import table entry for {s}:\n", .{dll_name}); + if (d.element(.@"header-name")) + try w.print("Import table entry for {s}:\n", .{dll_name}); try dumpHeader(d, Entry, &entry, struct {}); - try w.print( - \\ - \\ Ord Hint Name - \\ - , .{}); + if (d.element(.@"table-header")) + try w.print( + \\ + \\ Ord Hint Name + \\ + , .{}); const ilt_section = sectionContainingRva( rva_index, @@ -1565,7 +1611,7 @@ const coff = struct { const FormatIntField = struct { val: ?u64, - width: usize, + width: ?usize, zero_fill: bool, }; @@ -1574,14 +1620,22 @@ const coff = struct { val: anytype, params: struct { kind: ?FieldKind = null, - width: ?usize = null, + width: union(enum) { + fit_max, + auto, + explicit: usize, + } = .fit_max, zero_fill: bool = false, }, ) std.fmt.Alt(FormatIntField, intFieldString) { return .{ .data = .{ .val = if (d.redacted(params.kind)) null else val, - .width = params.width orelse @typeInfo(@TypeOf(val)).int.bits / 4, + .width = switch (params.width) { + .fit_max => @typeInfo(@TypeOf(val)).int.bits / 4, + .auto => null, + .explicit => |w| w, + }, .zero_fill = params.zero_fill, }, }; @@ -1594,7 +1648,7 @@ const coff = struct { .alignment = .right, .fill = if (field.zero_fill) '0' else ' ', }); - } else try w.splatByteAll('x', field.width); + } else try w.splatByteAll('x', field.width orelse 1); } fn dumpFlags(w: *Io.Writer, comptime fmt: []const u8, comptime T: type, flags: *const T, cols: u32) !void { @@ -1628,6 +1682,8 @@ const coff = struct { if (std.mem.startsWith(u8, name, "number_") or std.mem.startsWith(u8, name, "size")) return .size; + if (std.mem.startsWith(u8, name, "hint")) + return .ord; return null; } @@ -1645,7 +1701,7 @@ const coff = struct { switch (@typeInfo(field.type)) { .int => try d.w.print("{f} {s}\n", .{ fmtIntField(d, val.*, .{ .kind = comptime fieldKind(field.name), - .width = 16, + .width = .{ .explicit = 16 }, }), field.name }), .@"enum" => try d.w.print("{x: >16} {s} ({t})\n", .{ val.*, field.name, val.* }), .@"struct" => |s| { @@ -1690,7 +1746,9 @@ const usage = \\Options: \\ -h, --help Print this help and exit \\ --all-headers Alias for --file-headers --linker-member=2 --member-headers --section-headers --relocs --symbols - \\ --exports Display exported symbols. In the case of COFF import libraries, display import headers. + \\ --exports[=sort] Display exported symbols. + \\ In the case of COFF import libraries, displays the symbol list and import headers. + \\ Specify =sort to optionally sort the import headers by symbol name. \\ --file-headers Display file-format specific headers \\ --imports Display imported symbols \\ --linker-member[=1|2|longnames] (Coff) Display contents of the specified archive linker member (default 2) diff --git a/src/codegen/x86_64/Emit.zig b/src/codegen/x86_64/Emit.zig @@ -113,6 +113,7 @@ pub fn emitMir(emit: *Emit) Error!void { .default => true, .hidden, .protected => false, }, + .is_dll_import = @"extern".is_dll_import, .force_pcrel_direct = switch (@"extern".relocation) { .any => false, .pcrel => true, @@ -171,7 +172,13 @@ pub fn emitMir(emit: *Emit) Error!void { switch (lowered_inst.encoding.mnemonic) { .call => { reloc.target = .{ .branch = target }; - try emit.encodeInst(lowered_inst, reloc_info); + if (target.is_dll_import and emit.bin_file.cast(.coff2) != null) { + try emit.encodeInst(try .new(.none, .call, &.{ + .{ .mem = .initRip(.ptr, 0) }, + }, emit.lower.target), reloc_info); + } else { + try emit.encodeInst(lowered_inst, reloc_info); + } continue :lowered_inst; }, else => {}, @@ -247,7 +254,25 @@ pub fn emitMir(emit: *Emit) Error!void { else => unreachable, } } else if (emit.bin_file.cast(.coff2)) |_| { - switch (lowered_inst.encoding.mnemonic) { + if (reloc.target.is_dll_import) switch (lowered_inst.encoding.mnemonic) { + .lea => try emit.encodeInst(try .new(.none, .mov, &.{ + lowered_inst.ops[0], + .{ .mem = .initRip(.ptr, 0) }, + }, emit.lower.target), reloc_info), + .mov => { + try emit.encodeInst(try .new(.none, .mov, &.{ + lowered_inst.ops[0], + .{ .mem = .initRip(.ptr, 0) }, + }, emit.lower.target), reloc_info); + try emit.encodeInst(try .new(.none, .mov, &.{ + lowered_inst.ops[0], + .{ .mem = .initSib(lowered_inst.ops[reloc.op_index].mem.sib.ptr_size, .{ .base = .{ + .reg = lowered_inst.ops[0].reg.to64(), + } }) }, + }, emit.lower.target), &.{}); + }, + else => unreachable, + } else switch (lowered_inst.encoding.mnemonic) { .lea => try emit.encodeInst(try .new(.none, .lea, &.{ lowered_inst.ops[0], .{ .mem = .initRip(.none, 0) }, @@ -717,6 +742,7 @@ const RelocInfo = struct { const Symbol = struct { symbol: link.File.SymbolId, is_extern: bool, + is_dll_import: bool = false, force_pcrel_direct: bool = false, }; }; diff --git a/src/link/Coff.zig b/src/link/Coff.zig @@ -2103,7 +2103,7 @@ fn initHeaders( coff.mf.flags.block_size, .{ .read = true, .initialized = true }, )).symbol(coff).node(coff), - .{ .alignment = .@"4", .moved = true }, + .{ .alignment = .@"4" }, ); coff.nodes.appendAssumeCapacity(.import_directory_table); @@ -6115,6 +6115,7 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool { const imp_match = std.mem.startsWith(u8, global_name, imp_prefix); // Globals may have the __imp_ prefix already if they are undef externals from another input. + assert(sym.flags.dll_storage_class != .dllexport); const search_name, const is_imp = if (imp_match or sym.flags.dll_storage_class != .dllimport) .{ gn.name, imp_match } else name: { @@ -6722,10 +6723,14 @@ fn flushMoved(coff: *Coff, ni: MappedFile.Node.Index) !void { try input_symbol.si.flushMoved(coff); } }, - .import_directory_table => coff.targetStore( - &coff.dataDirectoryPtr(.IMPORT).virtual_address, - coff.computeNodeRva(ni), - ), + .import_directory_table => { + _, const size = ni.location(&coff.mf).resolve(&coff.mf); + if (size > 0) + coff.targetStore( + &coff.dataDirectoryPtr(.IMPORT).virtual_address, + coff.computeNodeRva(ni), + ); + }, .import_lookup_table => |import_index| coff.targetStore( &coff.importDirectoryEntryPtr(import_index).import_lookup_table_rva, coff.computeNodeRva(ni), @@ -6936,10 +6941,14 @@ fn flushResized(coff: *Coff, ni: MappedFile.Node.Index) !void { } }, .input_section => {}, - .import_directory_table => coff.targetStore( - &coff.dataDirectoryPtr(.IMPORT).size, - @intCast(size), - ), + .import_directory_table => { + const prev_size = coff.targetLoad(&coff.dataDirectoryPtr(.IMPORT).size); + coff.targetStore( + &coff.dataDirectoryPtr(.IMPORT).size, + @intCast(size), + ); + if (prev_size == 0) try coff.flushMoved(ni); + }, .import_lookup_table, .import_address_table, .import_hint_name_table, diff --git a/test/link.zig b/test/link.zig @@ -72,6 +72,7 @@ pub fn addCases(ctx: *LinkContext) void { if (ctx.includeTest("dynamic-lib-code")) |case| { const lib = case.addLibrary(.dynamic, .{ .name = "lib", + .name_target = false, .zig_source_bytes = \\export fn foo1() callconv(.c) u64 { \\ return 0x1122334411223344; @@ -91,9 +92,9 @@ pub fn addCases(ctx: *LinkContext) void { if (ctx.target.result.os.tag == .windows) { case.verifyObjdump(lib.getEmittedImplib(), &.{ "-s", - "--exports", + "--exports=sort", "--only-symbol=foo", - }, .{ .sub_name = "implib", .os = true }); + }, .{ .sub_name = "implib", .os = true, .arch = true }); } const exe = case.addExecutable(.{ @@ -118,9 +119,13 @@ pub fn addCases(ctx: *LinkContext) void { if (ctx.includeTest("dynamic-lib-data")) |case| { const lib = case.addLibrary(.dynamic, .{ .name = "lib", + .name_target = false, .zig_source_bytes = - \\export var array_foo: [2]u16 = .{ 0xffff, 0xabcd }; - \\export var strong_foo: usize = 0x1122334411223344; + \\export var foo_array: [2]u16 = .{ 0xffff, 0xabcd }; + \\export var foo_strong: usize = 0x1122334411223344; + \\comptime { + \\ @export(&foo_strong, .{ .name = "foo_strong_alias", .linkage = .strong }); + \\} , }); @@ -131,20 +136,24 @@ pub fn addCases(ctx: *LinkContext) void { }, .{}); if (ctx.target.result.os.tag == .windows) { - // TODO: objdump implib on windows + case.verifyObjdump(lib.getEmittedImplib(), &.{ + "-s", + "--exports=sort", + "--only-symbol=foo", + }, .{ .sub_name = "implib", .os = true, .arch = true }); } const exe = case.addExecutable(.{ .name = "test", .zig_source_bytes = - \\extern var array_foo: [2]u16; - \\extern var strong_foo: usize; - \\extern var strong_foo_alias: usize; \\pub fn main() !u8 { - \\ return @intFromBool(0x2244668822451255 != - \\ array_foo[1] + - \\ strong_foo + - \\ strong_foo_alias); + \\ const foo_array = @extern(*[2]u16, .{ .name = "foo_array", .is_dll_import = true }); + \\ const foo_strong = @extern(*usize, .{ .name = "foo_strong", .is_dll_import = true }); + \\ const foo_strong_alias = @extern(*usize, .{ .name = "foo_strong_alias", .is_dll_import = true }); + \\ return @intFromBool(0x2244668822451255 != + \\ foo_array[1] + + \\ foo_strong.* + + \\ foo_strong_alias.*); \\} , }); diff --git a/test/link/snapshots/abs-symbol.x86_64.dmp b/test/link/snapshots/abs-symbol.x86_64.dmp @@ -0,0 +1 @@ +xxxxxxxx ADDR32 xxxxxxxx UNDEF | foo diff --git a/test/link/snapshots/dynamic-lib-code.implib-windows.dmp b/test/link/snapshots/dynamic-lib-code.implib-windows.dmp @@ -19,7 +19,7 @@ xxxxxxxxxxxxxxxx size_of_data NAME name_type symbol name | foo1 import name | foo1 - dll | dynamic-lib-code-lib-x86_64-windows.win10...win11_dt-msvc-Debug-llvm-lld-libc.dll + dll | dynamic-lib-code-lib.dll 0 version 8664 machine (AMD64) 0 time_date_stamp @@ -29,4 +29,4 @@ xxxxxxxxxxxxxxxx size_of_data NAME name_type symbol name | foo2 import name | foo2 - dll | dynamic-lib-code-lib-x86_64-windows.win10...win11_dt-msvc-Debug-llvm-lld-libc.dll + dll | dynamic-lib-code-lib.dll diff --git a/test/link/snapshots/dynamic-lib-code.implib-x86_64-windows.dmp b/test/link/snapshots/dynamic-lib-code.implib-x86_64-windows.dmp @@ -0,0 +1,32 @@ + 0 date + 0 user_id + 0 group_id + 0 file_mode +xxxxxxxxxxxxxxxx size + second_linker type + | x symbols + | x members +xxxxxxxx __imp_foo1 +xxxxxxxx __imp_foo2 +xxxxxxxx foo1 +xxxxxxxx foo2 + 0 version + 8664 machine (AMD64) + 0 time_date_stamp +xxxxxxxxxxxxxxxx size_of_data +xxxxxxxxxxxxxxxx hint + CODE import_type + NAME name_type + symbol name | foo1 + import name | foo1 + dll | dynamic-lib-code-lib.dll + 0 version + 8664 machine (AMD64) + 0 time_date_stamp +xxxxxxxxxxxxxxxx size_of_data +xxxxxxxxxxxxxxxx hint + CODE import_type + NAME name_type + symbol name | foo2 + import name | foo2 + dll | dynamic-lib-code-lib.dll diff --git a/test/link/snapshots/dynamic-lib-data.dmp b/test/link/snapshots/dynamic-lib-data.dmp @@ -0,0 +1,14 @@ +Export directory: + 0 flags + 0 time_date_stamp + 0.00 version +xxxxxxxxxxxxxxxx name_rva + 1 ordinal_base +xxxxxxxxxxxxxxxx number_of_entries +xxxxxxxxxxxxxxxx number_of_names +xxxxxxxxxxxxxxxx export_address_table_rva +xxxxxxxxxxxxxxxx name_pointer_table_rva +xxxxxxxxxxxxxxxx ordinal_table_rva +xxxx xxxx xxxxxxxx | foo_array +xxxx xxxx xxxxxxxx | foo_strong +xxxx xxxx xxxxxxxx | foo_strong_alias diff --git a/test/link/snapshots/dynamic-lib-data.implib-x86_64-windows.dmp b/test/link/snapshots/dynamic-lib-data.implib-x86_64-windows.dmp @@ -0,0 +1,41 @@ + 0 date + 0 user_id + 0 group_id + 0 file_mode +xxxxxxxxxxxxxxxx size + second_linker type + | x symbols + | x members +xxxxxxxx __imp_foo_array +xxxxxxxx __imp_foo_strong +xxxxxxxx __imp_foo_strong_alias + 0 version + 8664 machine (AMD64) + 0 time_date_stamp +xxxxxxxxxxxxxxxx size_of_data +xxxxxxxxxxxxxxxx hint + DATA import_type + NAME name_type + symbol name | foo_array + import name | foo_array + dll | dynamic-lib-data-lib.dll + 0 version + 8664 machine (AMD64) + 0 time_date_stamp +xxxxxxxxxxxxxxxx size_of_data +xxxxxxxxxxxxxxxx hint + DATA import_type + NAME name_type + symbol name | foo_strong + import name | foo_strong + dll | dynamic-lib-data-lib.dll + 0 version + 8664 machine (AMD64) + 0 time_date_stamp +xxxxxxxxxxxxxxxx size_of_data +xxxxxxxxxxxxxxxx hint + DATA import_type + NAME name_type + symbol name | foo_strong_alias + import name | foo_strong_alias + dll | dynamic-lib-data-lib.dll diff --git a/test/src/Link.zig b/test/src/Link.zig @@ -185,7 +185,8 @@ pub const Case = struct { if (try snapshotNameInner(w, scope.link_libc, &sep)) try w.writeAll(if (ctx.link_libc) "libc" else "no-libc"); - if (sep == '-') try w.writeByte('.'); + if (sep == '-') sep = '.'; + try w.writeByte(sep); try w.writeAll("dmp"); return try snapshot_name.toOwnedSlice(); diff --git a/test/standalone/shared_library/mathtest.zig b/test/standalone/shared_library/mathtest.zig @@ -1,3 +1,5 @@ +export var exported_var: i32 = 9999; + export fn add(a: i32, b: i32) i32 { return a + b; } diff --git a/test/standalone/shared_library/test.c b/test/standalone/shared_library/test.c @@ -7,7 +7,16 @@ #include <stdint.h> int32_t add(int32_t a, int32_t b); +#if _WIN32 +#define IMPORT __declspec(dllimport) +#else +#define IMPORT +#endif + +extern IMPORT int32_t exported_var; + int main(int argc, char **argv) { assert(add(42, 1337) == 1379); + assert(exported_var == 9999); return 0; }