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