mirror of
https://codeberg.org/ziglang/zig.git
synced 2026-04-27 19:09:47 +03:00
Merge pull request #25414 from squeek502/mingw-def-implib
Support generating import libraries from mingw .def files without LLVM
This commit is contained in:
+21
-7
@@ -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,
|
||||
|
||||
@@ -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
@@ -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| {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 },
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user