Merge pull request #25414 from squeek502/mingw-def-implib

Support generating import libraries from mingw .def files without LLVM
This commit is contained in:
Andrew Kelley
2025-10-06 13:38:07 -07:00
committed by GitHub
7 changed files with 2231 additions and 105 deletions
+21 -7
View File
@@ -528,13 +528,11 @@ pub const SectionHeader = extern struct {
/// Applicable only to section headers in COFF objects.
pub fn getAlignment(self: SectionHeader) ?u16 {
if (self.flags.ALIGN == 0) return null;
return std.math.powi(u16, 2, self.flags.ALIGN - 1) catch unreachable;
return self.flags.ALIGN.toByteUnits();
}
pub fn setAlignment(self: *SectionHeader, new_alignment: u16) void {
assert(new_alignment > 0 and new_alignment <= 8192);
self.flags.ALIGN = @intCast(std.math.log2(new_alignment));
self.flags.ALIGN = .fromByteUnits(new_alignment);
}
pub fn isCode(self: SectionHeader) bool {
@@ -651,6 +649,16 @@ pub const SectionHeader = extern struct {
@"4096BYTES" = 13,
@"8192BYTES" = 14,
_,
pub fn toByteUnits(a: Align) ?u16 {
if (a == .NONE) return null;
return @as(u16, 1) << (@intFromEnum(a) - 1);
}
pub fn fromByteUnits(n: u16) Align {
std.debug.assert(std.math.isPowerOfTwo(n));
return @enumFromInt(@ctz(n) + 1);
}
};
};
};
@@ -925,6 +933,10 @@ pub const WeakExternalDefinition = struct {
flag: WeakExternalFlag,
unused: [10]u8,
pub fn sizeOf() usize {
return 18;
}
};
// https://github.com/tpn/winsdk-10/blob/master/Include/10.0.16299.0/km/ntimage.h
@@ -1338,14 +1350,16 @@ pub const Strtab = struct {
};
pub const ImportHeader = extern struct {
sig1: IMAGE.FILE.MACHINE,
sig2: u16,
/// Must be IMAGE_FILE_MACHINE_UNKNOWN
sig1: IMAGE.FILE.MACHINE = .UNKNOWN,
/// Must be 0xFFFF
sig2: u16 = 0xFFFF,
version: u16,
machine: IMAGE.FILE.MACHINE,
time_date_stamp: u32,
size_of_data: u32,
hint: u16,
types: packed struct(u32) {
types: packed struct(u16) {
type: ImportType,
name_type: ImportNameType,
reserved: u11,
-8
View File
@@ -338,14 +338,6 @@ extern fn ZigLLVMWriteArchive(
pub const ParseCommandLineOptions = ZigLLVMParseCommandLineOptions;
extern fn ZigLLVMParseCommandLineOptions(argc: usize, argv: [*]const [*:0]const u8) void;
pub const WriteImportLibrary = ZigLLVMWriteImportLibrary;
extern fn ZigLLVMWriteImportLibrary(
def_path: [*:0]const u8,
coff_machine: c_uint,
output_lib_path: [*:0]const u8,
kill_at: bool,
) bool;
pub const GetHostCPUName = LLVMGetHostCPUName;
extern fn LLVMGetHostCPUName() ?[*:0]u8;
+43 -28
View File
@@ -10,6 +10,13 @@ const Compilation = @import("../Compilation.zig");
const build_options = @import("build_options");
const Cache = std.Build.Cache;
const dev = @import("../dev.zig");
const def = @import("mingw/def.zig");
const implib = @import("mingw/implib.zig");
test {
_ = def;
_ = implib;
}
pub const CrtFile = enum {
crt2_o,
@@ -290,11 +297,6 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {
var o_dir = try comp.dirs.global_cache.handle.makeOpenPath(o_sub_path, .{});
defer o_dir.close();
const final_def_basename = try std.fmt.allocPrint(arena, "{s}.def", .{lib_name});
const def_final_path = try comp.dirs.global_cache.join(arena, &[_][]const u8{
"o", &digest, final_def_basename,
});
const aro = @import("aro");
var diagnostics: aro.Diagnostics = .{
.output = .{ .to_list = .{ .arena = .init(gpa) } },
@@ -312,7 +314,6 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {
defer std.debug.unlockStderrWriter();
nosuspend stderr.print("def file: {s}\n", .{def_file_path}) catch break :print;
nosuspend stderr.print("include dir: {s}\n", .{include_dir}) catch break :print;
nosuspend stderr.print("output path: {s}\n", .{def_final_path}) catch break :print;
}
try aro_comp.include_dirs.append(gpa, include_dir);
@@ -339,32 +340,46 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {
}
}
{
// new scope to ensure definition file is written before passing the path to WriteImportLibrary
const def_final_file = try o_dir.createFile(final_def_basename, .{ .truncate = true });
defer def_final_file.close();
var buffer: [1024]u8 = undefined;
var file_writer = def_final_file.writer(&buffer);
try pp.prettyPrintTokens(&file_writer.interface, .result_only);
try file_writer.interface.flush();
}
const members = members: {
var aw: std.Io.Writer.Allocating = .init(gpa);
errdefer aw.deinit();
try pp.prettyPrintTokens(&aw.writer, .result_only);
const input = try aw.toOwnedSliceSentinel(0);
defer gpa.free(input);
const machine_type = target.toCoffMachine();
var def_diagnostics: def.Diagnostics = undefined;
var module_def = def.parse(gpa, input, machine_type, .mingw, &def_diagnostics) catch |err| switch (err) {
error.OutOfMemory => |e| return e,
error.ParseError => {
var buffer: [64]u8 = undefined;
const w = std.debug.lockStderrWriter(&buffer);
defer std.debug.unlockStderrWriter();
try w.writeAll("error: ");
try def_diagnostics.writeMsg(w, input);
try w.writeByte('\n');
return error.WritingImportLibFailed;
},
};
defer module_def.deinit();
module_def.fixupForImportLibraryGeneration(machine_type);
break :members try implib.getMembers(gpa, module_def, machine_type);
};
defer members.deinit();
const lib_final_path = try std.fs.path.join(gpa, &.{ "o", &digest, final_lib_basename });
errdefer gpa.free(lib_final_path);
if (!build_options.have_llvm) return error.ZigCompilerNotBuiltWithLLVMExtensions;
const llvm_bindings = @import("../codegen/llvm/bindings.zig");
const def_final_path_z = try arena.dupeZ(u8, def_final_path);
const lib_final_path_z = try comp.dirs.global_cache.joinZ(arena, &.{lib_final_path});
if (llvm_bindings.WriteImportLibrary(
def_final_path_z.ptr,
@intFromEnum(target.toCoffMachine()),
lib_final_path_z.ptr,
true,
)) {
// TODO surface a proper error here
log.err("unable to turn {s}.def into {s}.lib", .{ lib_name, lib_name });
return error.WritingImportLibFailed;
{
const lib_final_file = try o_dir.createFile(final_lib_basename, .{ .truncate = true });
defer lib_final_file.close();
var buffer: [1024]u8 = undefined;
var file_writer = lib_final_file.writer(&buffer);
try implib.writeCoffArchive(gpa, &file_writer.interface, members);
try file_writer.interface.flush();
}
man.writeManifest() catch |err| {
+1079
View File
@@ -0,0 +1,1079 @@
const std = @import("std");
pub const ModuleDefinitionType = enum {
mingw,
};
pub const ModuleDefinition = struct {
exports: std.ArrayList(Export) = .empty,
name: ?[]const u8 = null,
base_address: usize = 0,
arena: std.heap.ArenaAllocator,
type: ModuleDefinitionType,
pub const Export = struct {
/// This may lack mangling, such as underscore prefixing and stdcall suffixing.
/// In a .def file, this is `foo` in `foo` or `bar` in `foo = bar`.
name: []const u8,
/// Note: This is currently only set by `fixupForImportLibraryGeneration`
mangled_symbol_name: ?[]const u8,
/// The external, exported name.
/// In a .def file, this is `foo` in `foo = bar`.
ext_name: ?[]const u8,
/// In a .def file, this is `bar` in `foo == bar`.
import_name: ?[]const u8,
/// In a .def file, this is `bar` in `foo EXPORTAS bar`.
export_as: ?[]const u8,
no_name: bool,
ordinal: u16,
type: std.coff.ImportType,
private: bool,
};
/// Modifies `exports` such that import library generation will
/// behave as expected. Based on LLVM's dlltool driver.
pub fn fixupForImportLibraryGeneration(self: *ModuleDefinition, machine_type: std.coff.IMAGE.FILE.MACHINE) void {
const kill_at = true;
for (self.exports.items) |*e| {
// If ExtName is set (if the "ExtName = Name" syntax was used), overwrite
// Name with ExtName and clear ExtName. When only creating an import
// library and not linking, the internal name is irrelevant. This avoids
// cases where writeImportLibrary tries to transplant decoration from
// symbol decoration onto ExtName.
if (e.ext_name) |ext_name| {
e.name = ext_name;
e.ext_name = null;
}
if (kill_at) {
if (e.import_name != null or std.mem.startsWith(u8, e.name, "?"))
continue;
if (machine_type == .I386) {
// By making sure E.SymbolName != E.Name for decorated symbols,
// writeImportLibrary writes these symbols with the type
// IMPORT_NAME_UNDECORATE.
e.mangled_symbol_name = e.name;
}
// Trim off the trailing decoration. Symbols will always have a
// starting prefix here (either _ for cdecl/stdcall, @ for fastcall
// or ? for C++ functions). Vectorcall functions won't have any
// fixed prefix, but the function base name will still be at least
// one char.
const name_len_without_at_suffix = std.mem.indexOfScalarPos(u8, e.name, 1, '@') orelse e.name.len;
e.name = e.name[0..name_len_without_at_suffix];
}
}
}
pub fn deinit(self: *const ModuleDefinition) void {
self.arena.deinit();
}
};
pub const Diagnostics = struct {
err: Error,
token: Token,
extra: Extra = .{ .none = {} },
pub const Extra = union {
none: void,
expected: Token.Tag,
};
pub const Error = enum {
invalid_byte,
unfinished_quoted_identifier,
/// `expected` is populated
expected_token,
expected_integer,
unknown_statement,
unimplemented,
};
fn formatToken(ctx: TokenFormatContext, writer: *std.Io.Writer) std.Io.Writer.Error!void {
switch (ctx.token.tag) {
.eof, .invalid => return writer.writeAll(ctx.token.tag.nameForErrorDisplay()),
else => return writer.writeAll(ctx.token.slice(ctx.source)),
}
}
const TokenFormatContext = struct {
token: Token,
source: []const u8,
};
fn fmtToken(self: Diagnostics, source: []const u8) std.fmt.Alt(TokenFormatContext, formatToken) {
return .{ .data = .{
.token = self.token,
.source = source,
} };
}
pub fn writeMsg(self: Diagnostics, writer: *std.Io.Writer, source: []const u8) !void {
switch (self.err) {
.invalid_byte => {
return writer.print("invalid byte '{f}'", .{std.ascii.hexEscape(self.token.slice(source), .upper)});
},
.unfinished_quoted_identifier => {
return writer.print("unfinished quoted identifier at '{f}', expected closing '\"'", .{self.fmtToken(source)});
},
.expected_token => {
return writer.print("expected '{s}', got '{f}'", .{ self.extra.expected.nameForErrorDisplay(), self.fmtToken(source) });
},
.expected_integer => {
return writer.print("expected integer, got '{f}'", .{self.fmtToken(source)});
},
.unimplemented => {
return writer.print("support for '{f}' has not yet been implemented", .{self.fmtToken(source)});
},
.unknown_statement => {
return writer.print("unknown/invalid statement syntax beginning with '{f}'", .{self.fmtToken(source)});
},
}
}
};
pub fn parse(
allocator: std.mem.Allocator,
source: [:0]const u8,
machine_type: std.coff.IMAGE.FILE.MACHINE,
module_definition_type: ModuleDefinitionType,
diagnostics: *Diagnostics,
) !ModuleDefinition {
var tokenizer = Tokenizer.init(source);
var parser = Parser.init(&tokenizer, machine_type, module_definition_type, diagnostics);
return parser.parse(allocator);
}
const Token = struct {
tag: Tag,
start: usize,
end: usize,
pub const keywords = std.StaticStringMap(Tag).initComptime(.{
.{ "BASE", .keyword_base },
.{ "CONSTANT", .keyword_constant },
.{ "DATA", .keyword_data },
.{ "EXPORTS", .keyword_exports },
.{ "EXPORTAS", .keyword_exportas },
.{ "HEAPSIZE", .keyword_heapsize },
.{ "LIBRARY", .keyword_library },
.{ "NAME", .keyword_name },
.{ "NONAME", .keyword_noname },
.{ "PRIVATE", .keyword_private },
.{ "STACKSIZE", .keyword_stacksize },
.{ "VERSION", .keyword_version },
});
pub const Tag = enum {
invalid,
eof,
identifier,
comma,
equal,
equal_equal,
keyword_base,
keyword_constant,
keyword_data,
keyword_exports,
keyword_exportas,
keyword_heapsize,
keyword_library,
keyword_name,
keyword_noname,
keyword_private,
keyword_stacksize,
keyword_version,
pub fn nameForErrorDisplay(self: Tag) []const u8 {
return switch (self) {
.invalid => "<invalid>",
.eof => "<eof>",
.identifier => "<identifier>",
.comma => ",",
.equal => "=",
.equal_equal => "==",
.keyword_base => "BASE",
.keyword_constant => "CONSTANT",
.keyword_data => "DATA",
.keyword_exports => "EXPORTS",
.keyword_exportas => "EXPORTAS",
.keyword_heapsize => "HEAPSIZE",
.keyword_library => "LIBRARY",
.keyword_name => "NAME",
.keyword_noname => "NONAME",
.keyword_private => "PRIVATE",
.keyword_stacksize => "STACKSIZE",
.keyword_version => "VERSION",
};
}
};
/// Returns a useful slice of the token, e.g. for quoted identifiers, this
/// will return a slice without the quotes included.
pub fn slice(self: Token, source: []const u8) []const u8 {
return source[self.start..self.end];
}
};
const Tokenizer = struct {
source: [:0]const u8,
index: usize,
error_context_token: ?Token = null,
pub fn init(source: [:0]const u8) Tokenizer {
return .{
.source = source,
.index = 0,
};
}
const State = enum {
start,
identifier_or_keyword,
quoted_identifier,
comment,
equal,
eof_or_invalid,
};
pub const Error = error{
InvalidByte,
UnfinishedQuotedIdentifier,
};
pub fn next(self: *Tokenizer) Error!Token {
var result: Token = .{
.tag = undefined,
.start = self.index,
.end = undefined,
};
state: switch (State.start) {
.start => switch (self.source[self.index]) {
0 => continue :state .eof_or_invalid,
'\r', '\n', ' ', '\t', '\x0B' => {
self.index += 1;
result.start = self.index;
continue :state .start;
},
';' => continue :state .comment,
'=' => continue :state .equal,
',' => {
result.tag = .comma;
self.index += 1;
},
'"' => continue :state .quoted_identifier,
else => continue :state .identifier_or_keyword,
},
.comment => {
self.index += 1;
switch (self.source[self.index]) {
0 => continue :state .eof_or_invalid,
'\n' => {
self.index += 1;
result.start = self.index;
continue :state .start;
},
else => continue :state .comment,
}
},
.equal => {
self.index += 1;
switch (self.source[self.index]) {
'=' => {
result.tag = .equal_equal;
self.index += 1;
},
else => result.tag = .equal,
}
},
.quoted_identifier => {
self.index += 1;
switch (self.source[self.index]) {
0 => {
self.error_context_token = .{
.tag = .eof,
.start = self.index,
.end = self.index,
};
return error.UnfinishedQuotedIdentifier;
},
'"' => {
result.tag = .identifier;
self.index += 1;
// Return the token unquoted
return .{
.tag = result.tag,
.start = result.start + 1,
.end = self.index - 1,
};
},
else => continue :state .quoted_identifier,
}
},
.identifier_or_keyword => {
self.index += 1;
switch (self.source[self.index]) {
0, '=', ',', ';', '\r', '\n', ' ', '\t', '\x0B' => {
const keyword = Token.keywords.get(self.source[result.start..self.index]);
result.tag = keyword orelse .identifier;
},
else => continue :state .identifier_or_keyword,
}
},
.eof_or_invalid => {
if (self.index == self.source.len) {
return .{
.tag = .eof,
.start = self.index,
.end = self.index,
};
}
self.error_context_token = .{
.tag = .invalid,
.start = self.index,
.end = self.index + 1,
};
return error.InvalidByte;
},
}
result.end = self.index;
return result;
}
};
test Tokenizer {
try testTokenizer(
\\foo
\\; hello
\\BASE
\\"bar"
\\
, &.{
.identifier,
.keyword_base,
.identifier,
});
}
fn testTokenizer(source: [:0]const u8, expected: []const Token.Tag) !void {
var tokenizer = Tokenizer.init(source);
for (expected) |expected_tag| {
const token = try tokenizer.next();
try std.testing.expectEqual(expected_tag, token.tag);
}
const last_token = try tokenizer.next();
try std.testing.expectEqual(.eof, last_token.tag);
}
pub const Parser = struct {
tokenizer: *Tokenizer,
diagnostics: *Diagnostics,
lookahead_tokenizer: Tokenizer,
machine_type: std.coff.IMAGE.FILE.MACHINE,
module_definition_type: ModuleDefinitionType,
pub fn init(
tokenizer: *Tokenizer,
machine_type: std.coff.IMAGE.FILE.MACHINE,
module_definition_type: ModuleDefinitionType,
diagnostics: *Diagnostics,
) Parser {
return .{
.tokenizer = tokenizer,
.machine_type = machine_type,
.module_definition_type = module_definition_type,
.diagnostics = diagnostics,
.lookahead_tokenizer = undefined,
};
}
pub const Error = error{ParseError} || std.mem.Allocator.Error;
pub fn parse(self: *Parser, allocator: std.mem.Allocator) Error!ModuleDefinition {
var module: ModuleDefinition = .{
.arena = .init(allocator),
.type = self.module_definition_type,
};
const arena = module.arena.allocator();
errdefer module.deinit();
while (true) {
const tok = try self.nextToken();
switch (tok.tag) {
.eof => break,
.keyword_library, .keyword_name => {
const is_library = tok.tag == .keyword_library;
const name = try self.lookaheadToken();
if (name.tag != .identifier) continue;
self.commitLookahead();
const base_tok = try self.lookaheadToken();
if (base_tok.tag == .keyword_base) {
self.commitLookahead();
_ = try self.expectToken(.equal);
module.base_address = try self.expectInteger(usize);
}
// Append .dll/.exe if there's no extension
const name_slice = name.slice(self.tokenizer.source);
module.name = if (std.fs.path.extension(name_slice).len == 0)
try std.mem.concat(arena, u8, &.{ name_slice, if (is_library) ".dll" else ".exe" })
else
try arena.dupe(u8, name_slice);
},
.keyword_exports => {
while (true) {
var name_tok = try self.lookaheadToken();
if (name_tok.tag != .identifier) break;
self.commitLookahead();
const ext_name_tok = ext_name: {
const equal = try self.lookaheadToken();
if (equal.tag != .equal) break :ext_name null;
self.commitLookahead();
// The syntax is `<ext_name> = <name>`, so we need to
// swap the current name token over to ext_name and use
// this token as the name.
const ext_name_tok = name_tok;
name_tok = try self.expectToken(.identifier);
break :ext_name ext_name_tok;
};
var name_needs_underscore = false;
var ext_name_needs_underscore = false;
if (self.machine_type == .I386) {
const is_decorated = isDecorated(name_tok.slice(self.tokenizer.source), self.module_definition_type);
const is_forward_target = ext_name_tok != null and std.mem.indexOfScalar(u8, name_tok.slice(self.tokenizer.source), '.') != null;
name_needs_underscore = !is_decorated and !is_forward_target;
if (ext_name_tok) |ext_name| {
ext_name_needs_underscore = !isDecorated(ext_name.slice(self.tokenizer.source), self.module_definition_type);
}
}
var import_name_tok: ?Token = null;
var export_as_tok: ?Token = null;
var ordinal: ?u16 = null;
var import_type: std.coff.ImportType = .CODE;
var private: bool = false;
var no_name: bool = false;
while (true) {
const arg_tok = try self.lookaheadToken();
switch (arg_tok.tag) {
.identifier => {
const slice = arg_tok.slice(self.tokenizer.source);
if (slice[0] != '@') break;
// foo @ 10
if (slice.len == 1) {
self.commitLookahead();
ordinal = try self.expectInteger(u16);
continue;
}
// foo @10
ordinal = std.fmt.parseUnsigned(u16, slice[1..], 0) catch {
// e.g. foo @bar, the @bar is presumed to be the start of a separate
// export (and there could be a newline between them)
break;
};
// finally safe to commit to consuming the token
self.commitLookahead();
const noname_tok = try self.lookaheadToken();
if (noname_tok.tag == .keyword_noname) {
self.commitLookahead();
no_name = true;
}
},
.equal_equal => {
self.commitLookahead();
import_name_tok = try self.expectToken(.identifier);
},
.keyword_data => {
self.commitLookahead();
import_type = .DATA;
},
.keyword_constant => {
self.commitLookahead();
import_type = .CONST;
},
.keyword_private => {
self.commitLookahead();
private = true;
},
.keyword_exportas => {
self.commitLookahead();
export_as_tok = try self.expectToken(.identifier);
},
else => break,
}
}
const name = if (name_needs_underscore)
try std.mem.concat(arena, u8, &.{ "_", name_tok.slice(self.tokenizer.source) })
else
try arena.dupe(u8, name_tok.slice(self.tokenizer.source));
const ext_name: ?[]const u8 = if (ext_name_tok) |ext_name| if (name_needs_underscore)
try std.mem.concat(arena, u8, &.{ "_", ext_name.slice(self.tokenizer.source) })
else
try arena.dupe(u8, ext_name.slice(self.tokenizer.source)) else null;
try module.exports.append(arena, .{
.name = name,
.mangled_symbol_name = null,
.ext_name = ext_name,
.import_name = if (import_name_tok) |imp_name| try arena.dupe(u8, imp_name.slice(self.tokenizer.source)) else null,
.export_as = if (export_as_tok) |export_as| try arena.dupe(u8, export_as.slice(self.tokenizer.source)) else null,
.no_name = no_name,
.ordinal = ordinal orelse 0,
.type = import_type,
.private = private,
});
}
},
.keyword_heapsize,
.keyword_stacksize,
.keyword_version,
=> return self.unimplemented(tok),
else => {
self.diagnostics.* = .{
.err = .unknown_statement,
.token = tok,
};
return error.ParseError;
},
}
}
return module;
}
fn isDecorated(symbol: []const u8, module_definition_type: ModuleDefinitionType) bool {
// In def files, the symbols can either be listed decorated or undecorated.
//
// - For cdecl symbols, only the undecorated form is allowed.
// - For fastcall and vectorcall symbols, both fully decorated or
// undecorated forms can be present.
// - For stdcall symbols in non-MinGW environments, the decorated form is
// fully decorated with leading underscore and trailing stack argument
// size - like "_Func@0".
// - In MinGW def files, a decorated stdcall symbol does not include the
// leading underscore though, like "Func@0".
// This function controls whether a leading underscore should be added to
// the given symbol name or not. For MinGW, treat a stdcall symbol name such
// as "Func@0" as undecorated, i.e. a leading underscore must be added.
// For non-MinGW, look for '@' in the whole string and consider "_Func@0"
// as decorated, i.e. don't add any more leading underscores.
// We can't check for a leading underscore here, since function names
// themselves can start with an underscore, while a second one still needs
// to be added.
if (std.mem.startsWith(u8, symbol, "@")) return true;
if (std.mem.indexOf(u8, symbol, "@@") != null) return true;
if (std.mem.startsWith(u8, symbol, "?")) return true;
if (module_definition_type != .mingw and std.mem.indexOfScalar(u8, symbol, '@') != null) return true;
return false;
}
fn expectInteger(self: *Parser, T: type) Error!T {
const tok = try self.nextToken();
blk: {
if (tok.tag != .identifier) break :blk;
return std.fmt.parseUnsigned(T, tok.slice(self.tokenizer.source), 0) catch break :blk;
}
self.diagnostics.* = .{
.err = .expected_integer,
.token = tok,
};
return error.ParseError;
}
fn unimplemented(self: *Parser, tok: Token) Error {
self.diagnostics.* = .{
.err = .unimplemented,
.token = tok,
};
return error.ParseError;
}
fn expectToken(self: *Parser, tag: Token.Tag) Error!Token {
const tok = try self.nextToken();
if (tok.tag != tag) {
self.diagnostics.* = .{
.err = .expected_token,
.token = tok,
.extra = .{ .expected = tag },
};
return error.ParseError;
}
return tok;
}
fn nextToken(self: *Parser) Error!Token {
return self.nextFromTokenizer(self.tokenizer);
}
fn lookaheadToken(self: *Parser) Error!Token {
self.lookahead_tokenizer = self.tokenizer.*;
return self.nextFromTokenizer(&self.lookahead_tokenizer);
}
fn commitLookahead(self: *Parser) void {
self.tokenizer.* = self.lookahead_tokenizer;
}
fn nextFromTokenizer(
self: *Parser,
tokenizer: *Tokenizer,
) Error!Token {
return tokenizer.next() catch |err| {
self.diagnostics.* = .{
.err = switch (err) {
error.InvalidByte => .invalid_byte,
error.UnfinishedQuotedIdentifier => .unfinished_quoted_identifier,
},
.token = tokenizer.error_context_token.?,
};
return error.ParseError;
};
}
};
test parse {
const source =
\\LIBRARY "foo"
\\; hello
\\EXPORTS
\\foo @ 10
\\bar @104
\\baz@4
\\foo == bar
\\alias = function
\\
\\data DATA
\\constant CONSTANT
\\
;
try testParse(.AMD64, source, "foo.dll", &[_]ModuleDefinition.Export{
.{
.name = "foo",
.mangled_symbol_name = null,
.ext_name = null,
.import_name = null,
.export_as = null,
.no_name = false,
.ordinal = 10,
.type = .CODE,
.private = false,
},
.{
.name = "bar",
.mangled_symbol_name = null,
.ext_name = null,
.import_name = null,
.export_as = null,
.no_name = false,
.ordinal = 104,
.type = .CODE,
.private = false,
},
.{
.name = "baz@4",
.mangled_symbol_name = null,
.ext_name = null,
.import_name = null,
.export_as = null,
.no_name = false,
.ordinal = 0,
.type = .CODE,
.private = false,
},
.{
.name = "foo",
.mangled_symbol_name = null,
.ext_name = null,
.import_name = "bar",
.export_as = null,
.no_name = false,
.ordinal = 0,
.type = .CODE,
.private = false,
},
.{
.name = "function",
.mangled_symbol_name = null,
.ext_name = "alias",
.import_name = null,
.export_as = null,
.no_name = false,
.ordinal = 0,
.type = .CODE,
.private = false,
},
.{
.name = "data",
.mangled_symbol_name = null,
.ext_name = null,
.import_name = null,
.export_as = null,
.no_name = false,
.ordinal = 0,
.type = .DATA,
.private = false,
},
.{
.name = "constant",
.mangled_symbol_name = null,
.ext_name = null,
.import_name = null,
.export_as = null,
.no_name = false,
.ordinal = 0,
.type = .CONST,
.private = false,
},
});
try testParse(.I386, source, "foo.dll", &[_]ModuleDefinition.Export{
.{
.name = "_foo",
.mangled_symbol_name = null,
.ext_name = null,
.import_name = null,
.export_as = null,
.no_name = false,
.ordinal = 10,
.type = .CODE,
.private = false,
},
.{
.name = "_bar",
.mangled_symbol_name = null,
.ext_name = null,
.import_name = null,
.export_as = null,
.no_name = false,
.ordinal = 104,
.type = .CODE,
.private = false,
},
.{
.name = "_baz@4",
.mangled_symbol_name = null,
.ext_name = null,
.import_name = null,
.export_as = null,
.no_name = false,
.ordinal = 0,
.type = .CODE,
.private = false,
},
.{
.name = "_foo",
.mangled_symbol_name = null,
.ext_name = null,
.import_name = "bar",
.export_as = null,
.no_name = false,
.ordinal = 0,
.type = .CODE,
.private = false,
},
.{
.name = "_function",
.mangled_symbol_name = null,
.ext_name = "_alias",
.import_name = null,
.export_as = null,
.no_name = false,
.ordinal = 0,
.type = .CODE,
.private = false,
},
.{
.name = "_data",
.mangled_symbol_name = null,
.ext_name = null,
.import_name = null,
.export_as = null,
.no_name = false,
.ordinal = 0,
.type = .DATA,
.private = false,
},
.{
.name = "_constant",
.mangled_symbol_name = null,
.ext_name = null,
.import_name = null,
.export_as = null,
.no_name = false,
.ordinal = 0,
.type = .CONST,
.private = false,
},
});
try testParse(.ARMNT, source, "foo.dll", &[_]ModuleDefinition.Export{
.{
.name = "foo",
.mangled_symbol_name = null,
.ext_name = null,
.import_name = null,
.export_as = null,
.no_name = false,
.ordinal = 10,
.type = .CODE,
.private = false,
},
.{
.name = "bar",
.mangled_symbol_name = null,
.ext_name = null,
.import_name = null,
.export_as = null,
.no_name = false,
.ordinal = 104,
.type = .CODE,
.private = false,
},
.{
.name = "baz@4",
.mangled_symbol_name = null,
.ext_name = null,
.import_name = null,
.export_as = null,
.no_name = false,
.ordinal = 0,
.type = .CODE,
.private = false,
},
.{
.name = "foo",
.mangled_symbol_name = null,
.ext_name = null,
.import_name = "bar",
.export_as = null,
.no_name = false,
.ordinal = 0,
.type = .CODE,
.private = false,
},
.{
.name = "function",
.mangled_symbol_name = null,
.ext_name = "alias",
.import_name = null,
.export_as = null,
.no_name = false,
.ordinal = 0,
.type = .CODE,
.private = false,
},
.{
.name = "data",
.mangled_symbol_name = null,
.ext_name = null,
.import_name = null,
.export_as = null,
.no_name = false,
.ordinal = 0,
.type = .DATA,
.private = false,
},
.{
.name = "constant",
.mangled_symbol_name = null,
.ext_name = null,
.import_name = null,
.export_as = null,
.no_name = false,
.ordinal = 0,
.type = .CONST,
.private = false,
},
});
try testParse(.ARM64, source, "foo.dll", &[_]ModuleDefinition.Export{
.{
.name = "foo",
.mangled_symbol_name = null,
.ext_name = null,
.import_name = null,
.export_as = null,
.no_name = false,
.ordinal = 10,
.type = .CODE,
.private = false,
},
.{
.name = "bar",
.mangled_symbol_name = null,
.ext_name = null,
.import_name = null,
.export_as = null,
.no_name = false,
.ordinal = 104,
.type = .CODE,
.private = false,
},
.{
.name = "baz@4",
.mangled_symbol_name = null,
.ext_name = null,
.import_name = null,
.export_as = null,
.no_name = false,
.ordinal = 0,
.type = .CODE,
.private = false,
},
.{
.name = "foo",
.mangled_symbol_name = null,
.ext_name = null,
.import_name = "bar",
.export_as = null,
.no_name = false,
.ordinal = 0,
.type = .CODE,
.private = false,
},
.{
.name = "function",
.mangled_symbol_name = null,
.ext_name = "alias",
.import_name = null,
.export_as = null,
.no_name = false,
.ordinal = 0,
.type = .CODE,
.private = false,
},
.{
.name = "data",
.mangled_symbol_name = null,
.ext_name = null,
.import_name = null,
.export_as = null,
.no_name = false,
.ordinal = 0,
.type = .DATA,
.private = false,
},
.{
.name = "constant",
.mangled_symbol_name = null,
.ext_name = null,
.import_name = null,
.export_as = null,
.no_name = false,
.ordinal = 0,
.type = .CONST,
.private = false,
},
});
}
test "ntdll" {
const source =
\\;
\\; Definition file of ntdll.dll
\\; Automatic generated by gendef
\\; written by Kai Tietz 2008
\\;
\\LIBRARY "ntdll.dll"
\\EXPORTS
\\RtlDispatchAPC@12
\\RtlActivateActivationContextUnsafeFast@0
;
try testParse(.AMD64, source, "ntdll.dll", &[_]ModuleDefinition.Export{
.{
.name = "RtlDispatchAPC@12",
.mangled_symbol_name = null,
.ext_name = null,
.import_name = null,
.export_as = null,
.no_name = false,
.ordinal = 0,
.type = .CODE,
.private = false,
},
.{
.name = "RtlActivateActivationContextUnsafeFast@0",
.mangled_symbol_name = null,
.ext_name = null,
.import_name = null,
.export_as = null,
.no_name = false,
.ordinal = 0,
.type = .CODE,
.private = false,
},
});
}
fn testParse(machine_type: std.coff.IMAGE.FILE.MACHINE, source: [:0]const u8, expected_module_name: []const u8, expected_exports: []const ModuleDefinition.Export) !void {
var diagnostics: Diagnostics = undefined;
const module = parse(std.testing.allocator, source, machine_type, .mingw, &diagnostics) catch |err| switch (err) {
error.OutOfMemory => |e| return e,
error.ParseError => {
const stderr = std.debug.lockStderrWriter(&.{});
defer std.debug.unlockStderrWriter();
try diagnostics.writeMsg(stderr, source);
try stderr.writeByte('\n');
return err;
},
};
defer module.deinit();
try std.testing.expectEqualStrings(expected_module_name, module.name orelse "");
try std.testing.expectEqual(expected_exports.len, module.exports.items.len);
for (expected_exports, module.exports.items) |expected, actual| {
try std.testing.expectEqualStrings(expected.name, actual.name);
try std.testing.expectEqualStrings(expected.export_as orelse "", actual.export_as orelse "");
try std.testing.expectEqualStrings(expected.ext_name orelse "", actual.ext_name orelse "");
try std.testing.expectEqualStrings(expected.import_name orelse "", actual.import_name orelse "");
try std.testing.expectEqualStrings(expected.mangled_symbol_name orelse "", actual.mangled_symbol_name orelse "");
try std.testing.expectEqual(expected.ordinal, actual.ordinal);
try std.testing.expectEqual(expected.no_name, actual.no_name);
try std.testing.expectEqual(expected.private, actual.private);
try std.testing.expectEqual(expected.type, actual.type);
}
}
test "parse errors" {
for (&[_]std.coff.IMAGE.FILE.MACHINE{ .AMD64, .I386, .ARMNT, .ARM64 }) |machine_type| {
try testParseErrorMsg("invalid byte '\\x00'", machine_type, "LIBRARY \x00");
try testParseErrorMsg("unfinished quoted identifier at '<eof>', expected closing '\"'", machine_type, "LIBRARY \"foo");
try testParseErrorMsg("expected '=', got 'foo'", machine_type, "LIBRARY foo BASE foo");
try testParseErrorMsg("expected integer, got 'foo'", machine_type, "EXPORTS foo @ foo");
try testParseErrorMsg("support for 'HEAPSIZE' has not yet been implemented", machine_type, "HEAPSIZE");
try testParseErrorMsg("unknown/invalid statement syntax beginning with 'LIB'", machine_type, "LIB");
}
}
fn testParseErrorMsg(expected_msg: []const u8, machine_type: std.coff.IMAGE.FILE.MACHINE, source: [:0]const u8) !void {
var diagnostics: Diagnostics = undefined;
_ = parse(std.testing.allocator, source, machine_type, .mingw, &diagnostics) catch |err| switch (err) {
error.OutOfMemory => |e| return e,
error.ParseError => {
var buf: [256]u8 = undefined;
var writer: std.Io.Writer = .fixed(&buf);
try diagnostics.writeMsg(&writer, source);
try std.testing.expectEqualStrings(expected_msg, writer.buffered());
return;
},
};
return error.UnexpectedSuccess;
}
+1088
View File
@@ -0,0 +1,1088 @@
const std = @import("std");
const def = @import("def.zig");
const Allocator = std.mem.Allocator;
// LLVM has some quirks/bugs around padding/size values.
// Emulating those quirks made it much easier to test this implementation against the LLVM
// implementation since we could just check if the .lib files are byte-for-byte identical.
// This remains set to true out of an abundance of caution.
const llvm_compat = true;
pub const WriteCoffArchiveError = error{TooManyMembers} || std.Io.Writer.Error || std.mem.Allocator.Error;
pub fn writeCoffArchive(
allocator: std.mem.Allocator,
writer: *std.Io.Writer,
members: Members,
) WriteCoffArchiveError!void {
// The second linker member of a COFF archive uses a 32-bit integer for the number of members field,
// but only 16-bit integers for the "array of 1-based indexes that map symbol names to archive
// member offsets." This means that the maximum number of *indexable* members is maxInt(u16) - 1.
if (members.list.items.len > std.math.maxInt(u16) - 1) return error.TooManyMembers;
try writer.writeAll(archive_start);
const member_offsets = try allocator.alloc(usize, members.list.items.len);
defer allocator.free(member_offsets);
{
var offset: usize = 0;
for (member_offsets, 0..) |*elem, i| {
elem.* = offset;
offset += archive_header_len;
offset += members.list.items[i].byteLenWithPadding();
}
}
var long_names: StringTable = .{};
defer long_names.deinit(allocator);
var symbol_to_member_index = std.StringArrayHashMap(usize).init(allocator);
defer symbol_to_member_index.deinit();
var string_table_len: usize = 0;
var num_symbols: usize = 0;
for (members.list.items, 0..) |member, i| {
for (member.symbol_names_for_import_lib) |symbol_name| {
const gop_result = try symbol_to_member_index.getOrPut(symbol_name);
// When building the symbol map, ignore duplicate symbol names.
// This can happen in cases like (using .def file syntax):
// _foo
// foo == _foo
if (gop_result.found_existing) continue;
gop_result.value_ptr.* = i;
string_table_len += symbol_name.len + 1;
num_symbols += 1;
}
if (member.needsLongName()) {
_ = try long_names.put(allocator, member.name);
}
}
const first_linker_member_len = 4 + (4 * num_symbols) + string_table_len;
const second_linker_member_len = 4 + (4 * members.list.items.len) + 4 + (2 * num_symbols) + string_table_len;
const long_names_len_including_header_and_padding = blk: {
if (long_names.map.count() == 0) break :blk 0;
break :blk archive_header_len + std.mem.alignForward(usize, long_names.data.items.len, 2);
};
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;
// https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#first-linker-member
try writeArchiveMemberHeader(writer, .linker_member, memberHeaderLen(first_linker_member_len), "0");
try writer.writeInt(u32, @intCast(num_symbols), .big);
for (symbol_to_member_index.values()) |member_i| {
const offset = member_offsets[member_i];
try writer.writeInt(u32, @intCast(first_member_offset + offset), .big);
}
for (symbol_to_member_index.keys()) |symbol_name| {
try writer.writeAll(symbol_name);
try writer.writeByte(0);
}
if (first_linker_member_len % 2 != 0) try writer.writeByte(if (llvm_compat) 0 else archive_pad_byte);
// https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#second-linker-member
try writeArchiveMemberHeader(writer, .linker_member, memberHeaderLen(second_linker_member_len), "0");
try writer.writeInt(u32, @intCast(members.list.items.len), .little);
for (member_offsets) |offset| {
try writer.writeInt(u32, @intCast(first_member_offset + offset), .little);
}
try writer.writeInt(u32, @intCast(num_symbols), .little);
// sort lexicographically
const C = struct {
keys: []const []const u8,
pub fn lessThan(ctx: @This(), a_index: usize, b_index: usize) bool {
return std.mem.lessThan(u8, ctx.keys[a_index], ctx.keys[b_index]);
}
};
symbol_to_member_index.sortUnstable(C{ .keys = symbol_to_member_index.keys() });
for (symbol_to_member_index.values()) |member_i| {
try writer.writeInt(u16, @intCast(member_i + 1), .little);
}
for (symbol_to_member_index.keys()) |symbol_name| {
try writer.writeAll(symbol_name);
try writer.writeByte(0);
}
if (first_linker_member_len % 2 != 0) try writer.writeByte(if (llvm_compat) 0 else archive_pad_byte);
// https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#longnames-member
if (long_names.data.items.len != 0) {
const written_len = long_names.data.items.len;
try writeLongNamesMemberHeader(writer, memberHeaderLen(written_len));
try writer.writeAll(long_names.data.items);
if (long_names.data.items.len % 2 != 0) try writer.writeByte(archive_pad_byte);
}
for (members.list.items) |member| {
const name: MemberName = if (member.needsLongName())
.{ .longname = long_names.getOffset(member.name).? }
else
.{ .name = member.name };
try writeArchiveMemberHeader(writer, name, member.bytes.len, "644");
try writer.writeAll(member.bytes);
if (member.bytes.len % 2 != 0) try writer.writeByte(archive_pad_byte);
}
try writer.flush();
}
const archive_start = "!<arch>\n";
const archive_header_end = "`\n";
const archive_pad_byte = '\n';
const archive_header_len = 60;
fn memberHeaderLen(len: usize) usize {
return if (llvm_compat)
// LLVM writes this with the padding byte included, likely a bug/mistake
std.mem.alignForward(usize, len, 2)
else
len;
}
const MemberName = union(enum) {
name: []const u8,
linker_member,
longnames_member,
longname: usize,
pub fn write(self: MemberName, writer: *std.Io.Writer) !void {
switch (self) {
.name => |name| {
try writer.writeAll(name);
try writer.writeByte('/');
try writer.splatByteAll(' ', 16 - (name.len + 1));
},
.linker_member => {
try writer.writeAll("/ ");
},
.longnames_member => {
try writer.writeAll("// ");
},
.longname => |offset| {
try writer.print("/{d: <15}", .{offset});
},
}
}
};
fn writeLongNamesMemberHeader(writer: *std.Io.Writer, size: usize) !void {
try (MemberName{ .longnames_member = {} }).write(writer);
try writer.splatByteAll(' ', archive_header_len - 16 - 10 - archive_header_end.len);
try writer.print("{d: <10}", .{size});
try writer.writeAll(archive_header_end);
}
fn writeArchiveMemberHeader(writer: *std.Io.Writer, name: MemberName, size: usize, mode: []const u8) !void {
try name.write(writer);
try writer.writeAll("0 "); // date
try writer.writeAll("0 "); // user id
try writer.writeAll("0 "); // group id
try writer.print("{s: <8}", .{mode}); // mode
try writer.print("{d: <10}", .{size});
try writer.writeAll(archive_header_end);
}
pub const Members = struct {
list: std.ArrayList(Member) = .empty,
arena: std.heap.ArenaAllocator,
pub const Member = struct {
bytes: []const u8,
name: []const u8,
symbol_names_for_import_lib: []const []const u8,
pub fn byteLenWithPadding(self: Member) usize {
return std.mem.alignForward(usize, self.bytes.len, 2);
}
pub fn needsLongName(self: Member) bool {
return self.name.len >= 16;
}
};
pub fn deinit(self: *const Members) void {
self.arena.deinit();
}
};
const GetMembersError = GetImportDescriptorError || GetShortImportError;
pub fn getMembers(
allocator: std.mem.Allocator,
module_def: def.ModuleDefinition,
machine_type: std.coff.IMAGE.FILE.MACHINE,
) GetMembersError!Members {
var members: Members = .{
.arena = std.heap.ArenaAllocator.init(allocator),
};
const arena = members.arena.allocator();
errdefer members.deinit();
try members.list.ensureTotalCapacity(arena, 3 + module_def.exports.items.len);
const module_import_name = try arena.dupe(u8, module_def.name orelse "");
const library = std.fs.path.stem(module_import_name);
const import_descriptor_symbol_name = try std.mem.concat(arena, u8, &.{
import_descriptor_prefix,
library,
});
const null_thunk_symbol_name = try std.mem.concat(arena, u8, &.{
null_thunk_data_prefix,
library,
null_thunk_data_suffix,
});
members.list.appendAssumeCapacity(try getImportDescriptor(arena, machine_type, module_import_name, import_descriptor_symbol_name, null_thunk_symbol_name));
members.list.appendAssumeCapacity(try getNullImportDescriptor(arena, machine_type, module_import_name));
members.list.appendAssumeCapacity(try getNullThunk(arena, machine_type, module_import_name, null_thunk_symbol_name));
const DeferredExport = struct {
name: []const u8,
e: *const def.ModuleDefinition.Export,
};
var renames: std.ArrayList(DeferredExport) = .empty;
defer renames.deinit(allocator);
var regular_imports: std.StringArrayHashMapUnmanaged([]const u8) = .empty;
defer regular_imports.deinit(allocator);
for (module_def.exports.items) |*e| {
if (e.private) continue;
const maybe_mangled_name = e.mangled_symbol_name orelse e.name;
const name = maybe_mangled_name;
if (e.ext_name) |ext_name| {
_ = ext_name;
@panic("TODO"); // impossible if fixupForImportLibraryGeneration is called
}
var import_name_type: std.coff.ImportNameType = undefined;
var export_name: ?[]const u8 = null;
if (e.no_name) {
import_name_type = .ORDINAL;
} else if (e.export_as) |export_as| {
import_name_type = .NAME_EXPORTAS;
export_name = export_as;
} else if (e.import_name) |import_name| {
if (machine_type == .I386 and std.mem.eql(u8, applyNameType(.NAME_UNDECORATE, name), import_name)) {
import_name_type = .NAME_UNDECORATE;
} else if (machine_type == .I386 and std.mem.eql(u8, applyNameType(.NAME_NOPREFIX, name), import_name)) {
import_name_type = .NAME_NOPREFIX;
} else if (isArm64EC(machine_type)) {
import_name_type = .NAME_EXPORTAS;
export_name = import_name;
} else if (std.mem.eql(u8, name, import_name)) {
import_name_type = .NAME;
} else {
try renames.append(allocator, .{
.name = name,
.e = e,
});
continue;
}
} else {
import_name_type = getNameType(maybe_mangled_name, e.name, machine_type, module_def.type);
}
try regular_imports.put(allocator, applyNameType(import_name_type, name), name);
try members.list.append(arena, try getShortImport(arena, module_import_name, name, export_name, machine_type, e.ordinal, e.type, import_name_type));
}
for (renames.items) |deferred| {
const import_name = deferred.e.import_name.?;
if (regular_imports.get(import_name)) |symbol| {
if (deferred.e.type == .CODE) {
try members.list.append(arena, try getWeakExternal(arena, module_import_name, symbol, deferred.name, .{
.imp_prefix = false,
.machine_type = machine_type,
}));
}
try members.list.append(arena, try getWeakExternal(arena, module_import_name, symbol, deferred.name, .{
.imp_prefix = true,
.machine_type = machine_type,
}));
} else {
try members.list.append(arena, try getShortImport(
arena,
module_import_name,
deferred.name,
deferred.e.import_name,
machine_type,
deferred.e.ordinal,
deferred.e.type,
.NAME_EXPORTAS,
));
}
}
return members;
}
/// Returns a slice of `name`
fn applyNameType(name_type: std.coff.ImportNameType, name: []const u8) []const u8 {
switch (name_type) {
.NAME_NOPREFIX, .NAME_UNDECORATE => {
if (name.len == 0) return name;
const unprefixed = switch (name[0]) {
'?', '@', '_' => name[1..],
else => name,
};
if (name_type == .NAME_UNDECORATE) {
var split = std.mem.splitScalar(u8, unprefixed, '@');
return split.first();
} else {
return unprefixed;
}
},
else => return name,
}
}
fn getNameType(
symbol: []const u8,
ext_name: []const u8,
machine_type: std.coff.IMAGE.FILE.MACHINE,
module_definition_type: def.ModuleDefinitionType,
) std.coff.ImportNameType {
// A decorated stdcall function in MSVC is exported with the
// type IMPORT_NAME, and the exported function name includes the
// the leading underscore. In MinGW on the other hand, a decorated
// stdcall function still omits the underscore (IMPORT_NAME_NOPREFIX).
if (std.mem.startsWith(u8, ext_name, "_") and
std.mem.indexOfScalar(u8, ext_name, '@') != null and
module_definition_type != .mingw)
return .NAME;
if (!std.mem.eql(u8, symbol, ext_name))
return .NAME_UNDECORATE;
if (machine_type == .I386 and std.mem.startsWith(u8, symbol, "_"))
return .NAME_NOPREFIX;
return .NAME;
}
fn is64Bit(machine_type: std.coff.IMAGE.FILE.MACHINE) bool {
return switch (machine_type) {
.AMD64, .ARM64, .ARM64EC, .ARM64X => true,
else => false,
};
}
fn isArm64EC(machine_type: std.coff.IMAGE.FILE.MACHINE) bool {
return switch (machine_type) {
.ARM64EC, .ARM64X => true,
else => false,
};
}
const null_import_descriptor_symbol_name = "__NULL_IMPORT_DESCRIPTOR";
const import_descriptor_prefix = "__IMPORT_DESCRIPTOR_";
const null_thunk_data_prefix = "\x7F";
const null_thunk_data_suffix = "_NULL_THUNK_DATA";
// past the string table length field
const first_string_table_entry_offset = @sizeOf(u32);
const first_string_table_entry = getNameBytesForStringTableOffset(first_string_table_entry_offset);
const byte_size_of_relocation = 10;
fn getNameBytesForStringTableOffset(offset: u32) [8]u8 {
var bytes = [_]u8{0} ** 8;
std.mem.writeInt(u32, bytes[4..8], offset, .little);
return bytes;
}
const GetImportDescriptorError = error{UnsupportedMachineType} || std.mem.Allocator.Error;
fn getImportDescriptor(
allocator: std.mem.Allocator,
machine_type: std.coff.IMAGE.FILE.MACHINE,
module_import_name: []const u8,
import_descriptor_symbol_name: []const u8,
null_thunk_symbol_name: []const u8,
) GetImportDescriptorError!Members.Member {
const number_of_sections = 2;
const number_of_symbols = 7;
const number_of_relocations = 3;
const pointer_to_idata2_data = @sizeOf(std.coff.Header) +
(@sizeOf(std.coff.SectionHeader) * number_of_sections);
const pointer_to_idata6_data = pointer_to_idata2_data +
@sizeOf(std.coff.ImportDirectoryEntry) +
(byte_size_of_relocation * number_of_relocations);
const pointer_to_symbol_table = pointer_to_idata6_data +
module_import_name.len + 1;
const string_table_byte_len = 4 +
(import_descriptor_symbol_name.len + 1) +
(null_import_descriptor_symbol_name.len + 1) +
(null_thunk_symbol_name.len + 1);
const total_byte_len = pointer_to_symbol_table +
(std.coff.Symbol.sizeOf() * number_of_symbols) +
string_table_byte_len;
const bytes = try allocator.alloc(u8, total_byte_len);
errdefer allocator.free(bytes);
var writer: std.Io.Writer = .fixed(bytes);
writer.writeStruct(std.coff.Header{
.machine = machine_type,
.number_of_sections = number_of_sections,
.time_date_stamp = 0,
.pointer_to_symbol_table = @intCast(pointer_to_symbol_table),
.number_of_symbols = number_of_symbols,
.size_of_optional_header = 0,
.flags = .{ .@"32BIT_MACHINE" = !is64Bit(machine_type) },
}, .little) catch unreachable;
writer.writeStruct(std.coff.SectionHeader{
.name = ".idata$2".*,
.virtual_size = 0,
.virtual_address = 0,
.size_of_raw_data = @sizeOf(std.coff.ImportDirectoryEntry),
.pointer_to_raw_data = pointer_to_idata2_data,
.pointer_to_relocations = pointer_to_idata2_data + @sizeOf(std.coff.ImportDirectoryEntry),
.pointer_to_linenumbers = 0,
.number_of_relocations = number_of_relocations,
.number_of_linenumbers = 0,
.flags = .{
.ALIGN = .@"4BYTES",
.CNT_INITIALIZED_DATA = true,
.MEM_WRITE = true,
.MEM_READ = true,
},
}, .little) catch unreachable;
writer.writeStruct(std.coff.SectionHeader{
.name = ".idata$6".*,
.virtual_size = 0,
.virtual_address = 0,
.size_of_raw_data = @intCast(module_import_name.len + 1),
.pointer_to_raw_data = pointer_to_idata6_data,
.pointer_to_relocations = 0,
.pointer_to_linenumbers = 0,
.number_of_relocations = 0,
.number_of_linenumbers = 0,
.flags = .{
.ALIGN = .@"2BYTES",
.CNT_INITIALIZED_DATA = true,
.MEM_WRITE = true,
.MEM_READ = true,
},
}, .little) catch unreachable;
// .idata$2
writer.writeStruct(std.coff.ImportDirectoryEntry{
.forwarder_chain = 0,
.import_address_table_rva = 0,
.import_lookup_table_rva = 0,
.name_rva = 0,
.time_date_stamp = 0,
}, .little) catch unreachable;
const relocation_rva_type = rvaRelocationTypeIndicator(machine_type) orelse return error.UnsupportedMachineType;
writeRelocation(&writer, .{
.virtual_address = @offsetOf(std.coff.ImportDirectoryEntry, "name_rva"),
.symbol_table_index = 2,
.type = relocation_rva_type,
}) catch unreachable;
writeRelocation(&writer, .{
.virtual_address = @offsetOf(std.coff.ImportDirectoryEntry, "import_lookup_table_rva"),
.symbol_table_index = 3,
.type = relocation_rva_type,
}) catch unreachable;
writeRelocation(&writer, .{
.virtual_address = @offsetOf(std.coff.ImportDirectoryEntry, "import_address_table_rva"),
.symbol_table_index = 4,
.type = relocation_rva_type,
}) catch unreachable;
// .idata$6
writer.writeAll(module_import_name) catch unreachable;
writer.writeByte(0) catch unreachable;
var string_table_offset: usize = first_string_table_entry_offset;
writeSymbol(&writer, .{
.name = first_string_table_entry,
.value = 0,
.section_number = @enumFromInt(1),
.type = .{
.base_type = .NULL,
.complex_type = .NULL,
},
.storage_class = .EXTERNAL,
.number_of_aux_symbols = 0,
}) catch unreachable;
string_table_offset += import_descriptor_symbol_name.len + 1;
writeSymbol(&writer, .{
.name = ".idata$2".*,
.value = 0,
.section_number = @enumFromInt(1),
.type = .{
.base_type = .NULL,
.complex_type = .NULL,
},
.storage_class = .SECTION,
.number_of_aux_symbols = 0,
}) catch unreachable;
writeSymbol(&writer, .{
.name = ".idata$6".*,
.value = 0,
.section_number = @enumFromInt(2),
.type = .{
.base_type = .NULL,
.complex_type = .NULL,
},
.storage_class = .STATIC,
.number_of_aux_symbols = 0,
}) catch unreachable;
writeSymbol(&writer, .{
.name = ".idata$4".*,
.value = 0,
.section_number = .UNDEFINED,
.type = .{
.base_type = .NULL,
.complex_type = .NULL,
},
.storage_class = .SECTION,
.number_of_aux_symbols = 0,
}) catch unreachable;
writeSymbol(&writer, .{
.name = ".idata$5".*,
.value = 0,
.section_number = .UNDEFINED,
.type = .{
.base_type = .NULL,
.complex_type = .NULL,
},
.storage_class = .SECTION,
.number_of_aux_symbols = 0,
}) catch unreachable;
writeSymbol(&writer, .{
.name = getNameBytesForStringTableOffset(@intCast(string_table_offset)),
.value = 0,
.section_number = .UNDEFINED,
.type = .{
.base_type = .NULL,
.complex_type = .NULL,
},
.storage_class = .EXTERNAL,
.number_of_aux_symbols = 0,
}) catch unreachable;
string_table_offset += null_import_descriptor_symbol_name.len + 1;
writeSymbol(&writer, .{
.name = getNameBytesForStringTableOffset(@intCast(string_table_offset)),
.value = 0,
.section_number = .UNDEFINED,
.type = .{
.base_type = .NULL,
.complex_type = .NULL,
},
.storage_class = .EXTERNAL,
.number_of_aux_symbols = 0,
}) catch unreachable;
string_table_offset += null_thunk_symbol_name.len + 1;
// string table
writer.writeInt(u32, @intCast(string_table_byte_len), .little) catch unreachable;
writer.writeAll(import_descriptor_symbol_name) catch unreachable;
writer.writeByte(0) catch unreachable;
writer.writeAll(null_import_descriptor_symbol_name) catch unreachable;
writer.writeByte(0) catch unreachable;
writer.writeAll(null_thunk_symbol_name) catch unreachable;
writer.writeByte(0) catch unreachable;
var symbol_names_for_import_lib = try allocator.alloc([]const u8, 1);
errdefer allocator.free(symbol_names_for_import_lib);
const duped_symbol_name = try allocator.dupe(u8, import_descriptor_symbol_name);
errdefer allocator.free(duped_symbol_name);
symbol_names_for_import_lib[0] = duped_symbol_name;
// Confirm byte length was calculated exactly correctly
std.debug.assert(writer.end == bytes.len);
return .{
.bytes = bytes,
.name = module_import_name,
.symbol_names_for_import_lib = symbol_names_for_import_lib,
};
}
fn getNullImportDescriptor(
allocator: std.mem.Allocator,
machine_type: std.coff.IMAGE.FILE.MACHINE,
module_import_name: []const u8,
) error{OutOfMemory}!Members.Member {
const number_of_sections = 1;
const number_of_symbols = 1;
const pointer_to_idata3_data = @sizeOf(std.coff.Header) +
(@sizeOf(std.coff.SectionHeader) * number_of_sections);
const pointer_to_symbol_table = pointer_to_idata3_data +
@sizeOf(std.coff.ImportDirectoryEntry);
const string_table_byte_len = 4 + null_import_descriptor_symbol_name.len + 1;
const total_byte_len = pointer_to_symbol_table +
(std.coff.Symbol.sizeOf() * number_of_symbols) +
string_table_byte_len;
const bytes = try allocator.alloc(u8, total_byte_len);
errdefer allocator.free(bytes);
var writer: std.Io.Writer = .fixed(bytes);
writer.writeStruct(std.coff.Header{
.machine = machine_type,
.number_of_sections = number_of_sections,
.time_date_stamp = 0,
.pointer_to_symbol_table = @intCast(pointer_to_symbol_table),
.number_of_symbols = number_of_symbols,
.size_of_optional_header = 0,
.flags = .{ .@"32BIT_MACHINE" = !is64Bit(machine_type) },
}, .little) catch unreachable;
writer.writeStruct(std.coff.SectionHeader{
.name = ".idata$3".*,
.virtual_size = 0,
.virtual_address = 0,
.size_of_raw_data = @sizeOf(std.coff.ImportDirectoryEntry),
.pointer_to_raw_data = pointer_to_idata3_data,
.pointer_to_relocations = 0,
.pointer_to_linenumbers = 0,
.number_of_relocations = 0,
.number_of_linenumbers = 0,
.flags = .{
.ALIGN = .@"4BYTES",
.CNT_INITIALIZED_DATA = true,
.MEM_WRITE = true,
.MEM_READ = true,
},
}, .little) catch unreachable;
writer.writeStruct(std.coff.ImportDirectoryEntry{
.forwarder_chain = 0,
.import_address_table_rva = 0,
.import_lookup_table_rva = 0,
.name_rva = 0,
.time_date_stamp = 0,
}, .little) catch unreachable;
writeSymbol(&writer, .{
.name = first_string_table_entry,
.value = 0,
.section_number = @enumFromInt(1),
.type = .{
.base_type = .NULL,
.complex_type = .NULL,
},
.storage_class = .EXTERNAL,
.number_of_aux_symbols = 0,
}) catch unreachable;
// string table
writer.writeInt(u32, string_table_byte_len, .little) catch unreachable;
writer.writeAll(null_import_descriptor_symbol_name) catch unreachable;
writer.writeByte(0) catch unreachable;
var symbol_names_for_import_lib = try allocator.alloc([]const u8, 1);
errdefer allocator.free(symbol_names_for_import_lib);
const duped_symbol_name = try allocator.dupe(u8, null_import_descriptor_symbol_name);
errdefer allocator.free(duped_symbol_name);
symbol_names_for_import_lib[0] = duped_symbol_name;
// Confirm byte length was calculated exactly correctly
std.debug.assert(writer.end == bytes.len);
return .{
.bytes = bytes,
.name = module_import_name,
.symbol_names_for_import_lib = symbol_names_for_import_lib,
};
}
fn getNullThunk(
allocator: std.mem.Allocator,
machine_type: std.coff.IMAGE.FILE.MACHINE,
module_import_name: []const u8,
null_thunk_symbol_name: []const u8,
) error{OutOfMemory}!Members.Member {
const number_of_sections = 2;
const number_of_symbols = 1;
const va_size: u32 = if (is64Bit(machine_type)) 8 else 4;
const pointer_to_idata5_data = @sizeOf(std.coff.Header) +
(@sizeOf(std.coff.SectionHeader) * number_of_sections);
const pointer_to_idata4_data = pointer_to_idata5_data + va_size;
const pointer_to_symbol_table = pointer_to_idata4_data + va_size;
const string_table_byte_len = 4 + null_thunk_symbol_name.len + 1;
const total_byte_len = pointer_to_symbol_table +
(std.coff.Symbol.sizeOf() * number_of_symbols) +
string_table_byte_len;
const bytes = try allocator.alloc(u8, total_byte_len);
errdefer allocator.free(bytes);
var writer: std.Io.Writer = .fixed(bytes);
writer.writeStruct(std.coff.Header{
.machine = machine_type,
.number_of_sections = number_of_sections,
.time_date_stamp = 0,
.pointer_to_symbol_table = @intCast(pointer_to_symbol_table),
.number_of_symbols = number_of_symbols,
.size_of_optional_header = 0,
.flags = .{ .@"32BIT_MACHINE" = !is64Bit(machine_type) },
}, .little) catch unreachable;
writer.writeStruct(std.coff.SectionHeader{
.name = ".idata$5".*,
.virtual_size = 0,
.virtual_address = 0,
.size_of_raw_data = va_size,
.pointer_to_raw_data = pointer_to_idata5_data,
.pointer_to_relocations = 0,
.pointer_to_linenumbers = 0,
.number_of_relocations = 0,
.number_of_linenumbers = 0,
.flags = .{
.ALIGN = if (is64Bit(machine_type))
.@"8BYTES"
else
.@"4BYTES",
.CNT_INITIALIZED_DATA = true,
.MEM_WRITE = true,
.MEM_READ = true,
},
}, .little) catch unreachable;
writer.writeStruct(std.coff.SectionHeader{
.name = ".idata$4".*,
.virtual_size = 0,
.virtual_address = 0,
.size_of_raw_data = va_size,
.pointer_to_raw_data = pointer_to_idata4_data,
.pointer_to_relocations = 0,
.pointer_to_linenumbers = 0,
.number_of_relocations = 0,
.number_of_linenumbers = 0,
.flags = .{
.ALIGN = if (is64Bit(machine_type))
.@"8BYTES"
else
.@"4BYTES",
.CNT_INITIALIZED_DATA = true,
.MEM_WRITE = true,
.MEM_READ = true,
},
}, .little) catch unreachable;
// .idata$5
writer.splatByteAll(0, va_size) catch unreachable;
// .idata$4
writer.splatByteAll(0, va_size) catch unreachable;
writeSymbol(&writer, .{
.name = first_string_table_entry,
.value = 0,
.section_number = @enumFromInt(1),
.type = .{
.base_type = .NULL,
.complex_type = .NULL,
},
.storage_class = .EXTERNAL,
.number_of_aux_symbols = 0,
}) catch unreachable;
// string table
writer.writeInt(u32, @intCast(string_table_byte_len), .little) catch unreachable;
writer.writeAll(null_thunk_symbol_name) catch unreachable;
writer.writeByte(0) catch unreachable;
var symbol_names_for_import_lib = try allocator.alloc([]const u8, 1);
errdefer allocator.free(symbol_names_for_import_lib);
const duped_symbol_name = try allocator.dupe(u8, null_thunk_symbol_name);
errdefer allocator.free(duped_symbol_name);
symbol_names_for_import_lib[0] = duped_symbol_name;
// Confirm byte length was calculated exactly correctly
std.debug.assert(writer.end == bytes.len);
return .{
.bytes = bytes,
.name = module_import_name,
.symbol_names_for_import_lib = symbol_names_for_import_lib,
};
}
const WeakExternalOptions = struct {
imp_prefix: bool,
machine_type: std.coff.IMAGE.FILE.MACHINE,
};
fn getWeakExternal(
arena: std.mem.Allocator,
module_import_name: []const u8,
sym: []const u8,
weak: []const u8,
options: WeakExternalOptions,
) error{OutOfMemory}!Members.Member {
const number_of_sections = 1;
const number_of_symbols = 4;
const number_of_weak_external_defs = 1;
const pointer_to_symbol_table = @sizeOf(std.coff.Header) +
(@sizeOf(std.coff.SectionHeader) * number_of_sections);
const symbol_names = try arena.alloc([]const u8, 2);
symbol_names[0] = if (options.imp_prefix)
try std.mem.concat(arena, u8, &.{ "__imp_", sym })
else
try arena.dupe(u8, sym);
symbol_names[1] = if (options.imp_prefix)
try std.mem.concat(arena, u8, &.{ "__imp_", weak })
else
try arena.dupe(u8, weak);
const string_table_byte_len = 4 + symbol_names[0].len + 1 + symbol_names[1].len + 1;
const total_byte_len = pointer_to_symbol_table +
(std.coff.Symbol.sizeOf() * number_of_symbols) +
(std.coff.WeakExternalDefinition.sizeOf() * number_of_weak_external_defs) +
string_table_byte_len;
const bytes = try arena.alloc(u8, total_byte_len);
errdefer arena.free(bytes);
var writer: std.Io.Writer = .fixed(bytes);
writer.writeStruct(std.coff.Header{
.machine = options.machine_type,
.number_of_sections = number_of_sections,
.time_date_stamp = 0,
.pointer_to_symbol_table = @intCast(pointer_to_symbol_table),
.number_of_symbols = number_of_symbols + number_of_weak_external_defs,
.size_of_optional_header = 0,
.flags = .{},
}, .little) catch unreachable;
writer.writeStruct(std.coff.SectionHeader{
.name = ".drectve".*,
.virtual_size = 0,
.virtual_address = 0,
.size_of_raw_data = 0,
.pointer_to_raw_data = 0,
.pointer_to_relocations = 0,
.pointer_to_linenumbers = 0,
.number_of_relocations = 0,
.number_of_linenumbers = 0,
.flags = .{
.LNK_INFO = true,
.LNK_REMOVE = true,
},
}, .little) catch unreachable;
writeSymbol(&writer, .{
.name = "@comp.id".*,
.value = 0,
.section_number = .ABSOLUTE,
.type = .{
.base_type = .NULL,
.complex_type = .NULL,
},
.storage_class = .STATIC,
.number_of_aux_symbols = 0,
}) catch unreachable;
writeSymbol(&writer, .{
.name = "@feat.00".*,
.value = 0,
.section_number = .ABSOLUTE,
.type = .{
.base_type = .NULL,
.complex_type = .NULL,
},
.storage_class = .STATIC,
.number_of_aux_symbols = 0,
}) catch unreachable;
var string_table_offset: usize = first_string_table_entry_offset;
writeSymbol(&writer, .{
.name = first_string_table_entry,
.value = 0,
.section_number = @enumFromInt(0),
.type = .{
.base_type = .NULL,
.complex_type = .NULL,
},
.storage_class = .EXTERNAL,
.number_of_aux_symbols = 0,
}) catch unreachable;
string_table_offset += symbol_names[0].len + 1;
writeSymbol(&writer, .{
.name = getNameBytesForStringTableOffset(@intCast(string_table_offset)),
.value = 0,
.section_number = @enumFromInt(0),
.type = .{
.base_type = .NULL,
.complex_type = .NULL,
},
.storage_class = .WEAK_EXTERNAL,
.number_of_aux_symbols = 1,
}) catch unreachable;
writeWeakExternalDefinition(&writer, .{
.tag_index = 2,
.flag = .SEARCH_ALIAS,
.unused = @splat(0),
}) catch unreachable;
// string table
writer.writeInt(u32, @intCast(string_table_byte_len), .little) catch unreachable;
writer.writeAll(symbol_names[0]) catch unreachable;
writer.writeByte(0) catch unreachable;
writer.writeAll(symbol_names[1]) catch unreachable;
writer.writeByte(0) catch unreachable;
// Confirm byte length was calculated exactly correctly
std.debug.assert(writer.end == bytes.len);
return .{
.bytes = bytes,
.name = module_import_name,
.symbol_names_for_import_lib = symbol_names,
};
}
const GetShortImportError = error{UnknownImportType} || std.mem.Allocator.Error;
fn getShortImport(
arena: std.mem.Allocator,
module_import_name: []const u8,
sym: []const u8,
export_name: ?[]const u8,
machine_type: std.coff.IMAGE.FILE.MACHINE,
ordinal_hint: u16,
import_type: std.coff.ImportType,
name_type: std.coff.ImportNameType,
) GetShortImportError!Members.Member {
var size_of_data = module_import_name.len + 1 + sym.len + 1;
if (export_name) |name| size_of_data += name.len + 1;
const total_byte_len = @sizeOf(std.coff.ImportHeader) + size_of_data;
const bytes = try arena.alloc(u8, total_byte_len);
errdefer arena.free(bytes);
var writer = std.Io.Writer.fixed(bytes);
writer.writeStruct(std.coff.ImportHeader{
.version = 0,
.machine = machine_type,
.time_date_stamp = 0,
.size_of_data = @intCast(size_of_data),
.hint = ordinal_hint,
.types = .{
.type = import_type,
.name_type = name_type,
.reserved = 0,
},
}, .little) catch unreachable;
writer.writeAll(sym) catch unreachable;
writer.writeByte(0) catch unreachable;
writer.writeAll(module_import_name) catch unreachable;
writer.writeByte(0) catch unreachable;
if (export_name) |name| {
writer.writeAll(name) catch unreachable;
writer.writeByte(0) catch unreachable;
}
var symbol_names_for_import_lib: std.ArrayList([]const u8) = try .initCapacity(arena, 2);
switch (import_type) {
.CODE, .CONST => {
symbol_names_for_import_lib.appendAssumeCapacity(try std.mem.concat(arena, u8, &.{ "__imp_", sym }));
symbol_names_for_import_lib.appendAssumeCapacity(try arena.dupe(u8, sym));
},
.DATA => {
symbol_names_for_import_lib.appendAssumeCapacity(try std.mem.concat(arena, u8, &.{ "__imp_", sym }));
},
else => return error.UnknownImportType,
}
// Confirm byte length was calculated exactly correctly
std.debug.assert(writer.end == bytes.len);
return .{
.bytes = bytes,
.name = module_import_name,
.symbol_names_for_import_lib = try symbol_names_for_import_lib.toOwnedSlice(arena),
};
}
fn writeSymbol(writer: *std.Io.Writer, symbol: std.coff.Symbol) !void {
try writer.writeAll(&symbol.name);
try writer.writeInt(u32, symbol.value, .little);
try writer.writeInt(u16, @intFromEnum(symbol.section_number), .little);
try writer.writeInt(u8, @intFromEnum(symbol.type.base_type), .little);
try writer.writeInt(u8, @intFromEnum(symbol.type.complex_type), .little);
try writer.writeInt(u8, @intFromEnum(symbol.storage_class), .little);
try writer.writeInt(u8, symbol.number_of_aux_symbols, .little);
}
fn writeWeakExternalDefinition(writer: *std.Io.Writer, weak_external: std.coff.WeakExternalDefinition) !void {
try writer.writeInt(u32, weak_external.tag_index, .little);
try writer.writeInt(u32, @intFromEnum(weak_external.flag), .little);
try writer.writeAll(&weak_external.unused);
}
fn writeRelocation(writer: *std.Io.Writer, relocation: std.coff.Relocation) !void {
try writer.writeInt(u32, relocation.virtual_address, .little);
try writer.writeInt(u32, relocation.symbol_table_index, .little);
try writer.writeInt(u16, relocation.type, .little);
}
// https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#type-indicators
pub fn rvaRelocationTypeIndicator(target: std.coff.IMAGE.FILE.MACHINE) ?u16 {
return switch (target) {
.AMD64 => @intFromEnum(std.coff.IMAGE.REL.AMD64.ADDR32NB),
.I386 => @intFromEnum(std.coff.IMAGE.REL.I386.DIR32NB),
.ARMNT => @intFromEnum(std.coff.IMAGE.REL.ARM.ADDR32NB),
.ARM64, .ARM64EC, .ARM64X => @intFromEnum(std.coff.IMAGE.REL.ARM64.ADDR32NB),
.IA64 => @intFromEnum(std.coff.IMAGE.REL.IA64.DIR32NB),
else => null,
};
}
const StringTable = struct {
data: std.ArrayList(u8) = .empty,
map: std.HashMapUnmanaged(u32, void, std.hash_map.StringIndexContext, std.hash_map.default_max_load_percentage) = .empty,
pub fn deinit(self: *StringTable, allocator: Allocator) void {
self.data.deinit(allocator);
self.map.deinit(allocator);
}
pub fn put(self: *StringTable, allocator: Allocator, value: []const u8) !u32 {
const result = try self.map.getOrPutContextAdapted(
allocator,
value,
std.hash_map.StringIndexAdapter{ .bytes = &self.data },
.{ .bytes = &self.data },
);
if (result.found_existing) {
return result.key_ptr.*;
}
try self.data.ensureUnusedCapacity(allocator, value.len + 1);
const offset: u32 = @intCast(self.data.items.len);
self.data.appendSliceAssumeCapacity(value);
self.data.appendAssumeCapacity(0);
result.key_ptr.* = offset;
return offset;
}
pub fn get(self: StringTable, offset: u32) []const u8 {
std.debug.assert(offset < self.data.items.len);
return std.mem.sliceTo(@as([*:0]const u8, @ptrCast(self.data.items.ptr + offset)), 0);
}
pub fn getOffset(self: *StringTable, value: []const u8) ?u32 {
return self.map.getKeyAdapted(
value,
std.hash_map.StringIndexAdapter{ .bytes = &self.data },
);
}
};
-59
View File
@@ -39,9 +39,6 @@
#include <llvm/Passes/StandardInstrumentations.h>
#include <llvm/Object/Archive.h>
#include <llvm/Object/ArchiveWriter.h>
#include <llvm/Object/COFF.h>
#include <llvm/Object/COFFImportFile.h>
#include <llvm/Object/COFFModuleDefinition.h>
#include <llvm/PassRegistry.h>
#include <llvm/Support/CommandLine.h>
#include <llvm/Support/FileSystem.h>
@@ -475,62 +472,6 @@ void ZigLLVMParseCommandLineOptions(size_t argc, const char *const *argv) {
cl::ParseCommandLineOptions(argc, argv);
}
bool ZigLLVMWriteImportLibrary(const char *def_path, unsigned int coff_machine,
const char *output_lib_path, bool kill_at)
{
COFF::MachineTypes machine = static_cast<COFF::MachineTypes>(coff_machine);
auto bufOrErr = MemoryBuffer::getFile(def_path);
if (!bufOrErr) {
return false;
}
MemoryBuffer& buf = *bufOrErr.get();
Expected<object::COFFModuleDefinition> def =
object::parseCOFFModuleDefinition(buf, machine, /* MingwDef */ true);
if (!def) {
return true;
}
// The exports-juggling code below is ripped from LLVM's DlltoolDriver.cpp
// If ExtName is set (if the "ExtName = Name" syntax was used), overwrite
// Name with ExtName and clear ExtName. When only creating an import
// library and not linking, the internal name is irrelevant. This avoids
// cases where writeImportLibrary tries to transplant decoration from
// symbol decoration onto ExtName.
for (object::COFFShortExport& E : def->Exports) {
if (!E.ExtName.empty()) {
E.Name = E.ExtName;
E.ExtName.clear();
}
}
if (kill_at) {
for (object::COFFShortExport& E : def->Exports) {
if (!E.ImportName.empty() || (!E.Name.empty() && E.Name[0] == '?'))
continue;
if (machine == COFF::IMAGE_FILE_MACHINE_I386) {
// By making sure E.SymbolName != E.Name for decorated symbols,
// writeImportLibrary writes these symbols with the type
// IMPORT_NAME_UNDECORATE.
E.SymbolName = E.Name;
}
// Trim off the trailing decoration. Symbols will always have a
// starting prefix here (either _ for cdecl/stdcall, @ for fastcall
// or ? for C++ functions). Vectorcall functions won't have any
// fixed prefix, but the function base name will still be at least
// one char.
E.Name = E.Name.substr(0, E.Name.find('@', 1));
}
}
return static_cast<bool>(
object::writeImportLibrary(def->OutputFile, output_lib_path,
def->Exports, machine, /* MinGW */ true));
}
bool ZigLLVMWriteArchive(const char *archive_name, const char **file_names, size_t file_name_count,
ZigLLVMArchiveKind archive_kind)
{
-3
View File
@@ -124,7 +124,4 @@ ZIG_EXTERN_C bool ZigLLDLinkWasm(int argc, const char **argv, bool can_exit_earl
ZIG_EXTERN_C bool ZigLLVMWriteArchive(const char *archive_name, const char **file_names, size_t file_name_count,
ZigLLVMArchiveKind archive_kind);
ZIG_EXTERN_C bool ZigLLVMWriteImportLibrary(const char *def_path, unsigned int coff_machine,
const char *output_lib_path, bool kill_at);
#endif