Merge pull request #8282 from kubkon/zld

macho: upstream zld linker
This commit is contained in:
Jakub Konka 2021-03-18 19:14:17 +01:00 committed by GitHub
commit 17c066e925
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 3794 additions and 306 deletions

View File

@ -564,7 +564,14 @@ set(ZIG_STAGE2_SOURCES
"${CMAKE_SOURCE_DIR}/src/link/Coff.zig"
"${CMAKE_SOURCE_DIR}/src/link/Elf.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/Archive.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/CodeSignature.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/DebugSymbols.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/Object.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/Trie.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/Zld.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/bind.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/commands.zig"
"${CMAKE_SOURCE_DIR}/src/link/Wasm.zig"
"${CMAKE_SOURCE_DIR}/src/link/C/zig.h"
"${CMAKE_SOURCE_DIR}/src/link/msdos-stub.bin"

View File

@ -250,24 +250,6 @@ pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, c
resetSegfaultHandler();
}
if (comptime std.Target.current.isDarwin() and std.Target.current.cpu.arch == .aarch64)
nosuspend {
// As a workaround for not having threadlocal variable support in LLD for this target,
// we have a simpler panic implementation that does not use threadlocal variables.
// TODO https://github.com/ziglang/zig/issues/7527
const stderr = io.getStdErr().writer();
if (@atomicRmw(u8, &panicking, .Add, 1, .SeqCst) == 0) {
stderr.print("panic: " ++ format ++ "\n", args) catch os.abort();
if (trace) |t| {
dumpStackTrace(t.*);
}
dumpCurrentStackTrace(first_trace_addr);
} else {
stderr.print("Panicked during a panic. Aborting.\n", .{}) catch os.abort();
}
os.abort();
};
nosuspend switch (panic_stage) {
0 => {
panic_stage = 1;

View File

@ -1227,6 +1227,24 @@ pub const S_ATTR_EXT_RELOC = 0x200;
/// section has local relocation entries
pub const S_ATTR_LOC_RELOC = 0x100;
/// template of initial values for TLVs
pub const S_THREAD_LOCAL_REGULAR = 0x11;
/// template of initial values for TLVs
pub const S_THREAD_LOCAL_ZEROFILL = 0x12;
/// TLV descriptors
pub const S_THREAD_LOCAL_VARIABLES = 0x13;
/// pointers to TLV descriptors
pub const S_THREAD_LOCAL_VARIABLE_POINTERS = 0x14;
/// functions to call to initialize TLV values
pub const S_THREAD_LOCAL_INIT_FUNCTION_POINTERS = 0x15;
/// 32-bit offsets to initializers
pub const S_INIT_FUNC_OFFSETS = 0x16;
pub const cpu_type_t = integer_t;
pub const cpu_subtype_t = integer_t;
pub const integer_t = c_int;
@ -1597,3 +1615,17 @@ pub const GenericBlob = extern struct {
/// Total length of blob
length: u32,
};
/// The LC_DATA_IN_CODE load commands uses a linkedit_data_command
/// to point to an array of data_in_code_entry entries. Each entry
/// describes a range of data in a code section.
pub const data_in_code_entry = extern struct {
/// From mach_header to start of data range.
offset: u32,
/// Number of bytes in data range.
length: u16,
/// A DICE_KIND value.
kind: u16,
};

View File

@ -221,7 +221,8 @@ pub const Instruction = union(enum) {
offset: u12,
opc: u2,
op1: u2,
fixed: u4 = 0b111_0,
v: u1,
fixed: u3 = 0b111,
size: u2,
},
LoadStorePairOfRegisters: packed struct {
@ -505,6 +506,7 @@ pub const Instruction = union(enum) {
.offset = offset.toU12(),
.opc = opc,
.op1 = op1,
.v = 0,
.size = 0b10,
},
};
@ -517,6 +519,7 @@ pub const Instruction = union(enum) {
.offset = offset.toU12(),
.opc = opc,
.op1 = op1,
.v = 0,
.size = 0b11,
},
};

View File

@ -26,6 +26,7 @@ const target_util = @import("../target.zig");
const DebugSymbols = @import("MachO/DebugSymbols.zig");
const Trie = @import("MachO/Trie.zig");
const CodeSignature = @import("MachO/CodeSignature.zig");
const Zld = @import("MachO/Zld.zig");
usingnamespace @import("MachO/commands.zig");
@ -632,7 +633,74 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void {
if (!mem.eql(u8, the_object_path, full_out_path)) {
try fs.cwd().copyFile(the_object_path, fs.cwd(), full_out_path, .{});
}
} else {
} else outer: {
const use_zld = blk: {
if (self.base.options.is_native_os and self.base.options.system_linker_hack) {
// If the user forces the use of ld64, make sure we are running native!
break :blk false;
}
if (self.base.options.target.cpu.arch == .aarch64) {
// On aarch64, always use zld.
break :blk true;
}
if (self.base.options.link_libcpp or
self.base.options.output_mode == .Lib or
self.base.options.linker_script != null)
{
// Fallback to LLD in this handful of cases on x86_64 only.
break :blk false;
}
break :blk true;
};
if (use_zld) {
var zld = Zld.init(self.base.allocator);
defer zld.deinit();
zld.arch = target.cpu.arch;
var input_files = std.ArrayList([]const u8).init(self.base.allocator);
defer input_files.deinit();
// Positional arguments to the linker such as object files.
try input_files.appendSlice(self.base.options.objects);
for (comp.c_object_table.items()) |entry| {
try input_files.append(entry.key.status.success.object_path);
}
if (module_obj_path) |p| {
try input_files.append(p);
}
try input_files.append(comp.compiler_rt_static_lib.?.full_object_path);
// libc++ dep
if (self.base.options.link_libcpp) {
try input_files.append(comp.libcxxabi_static_lib.?.full_object_path);
try input_files.append(comp.libcxx_static_lib.?.full_object_path);
}
if (self.base.options.verbose_link) {
var argv = std.ArrayList([]const u8).init(self.base.allocator);
defer argv.deinit();
try argv.append("zig");
try argv.append("ld");
try argv.ensureCapacity(input_files.items.len);
for (input_files.items) |f| {
argv.appendAssumeCapacity(f);
}
try argv.append("-o");
try argv.append(full_out_path);
Compilation.dump_argv(argv.items);
}
try zld.link(input_files.items, full_out_path);
break :outer;
}
// Create an LLD command line and invoke it.
var argv = std.ArrayList([]const u8).init(self.base.allocator);
defer argv.deinit();
@ -903,119 +971,6 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void {
log.warn("unexpected LLD stderr:\n{s}", .{stderr});
}
}
// At this stage, LLD has done its job. It is time to patch the resultant
// binaries up!
const out_file = try directory.handle.openFile(self.base.options.emit.?.sub_path, .{ .write = true });
try self.parseFromFile(out_file);
if (self.libsystem_cmd_index == null and self.header.?.filetype == macho.MH_EXECUTE) {
const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment;
const text_section = text_segment.sections.items[self.text_section_index.?];
const after_last_cmd_offset = self.header.?.sizeofcmds + @sizeOf(macho.mach_header_64);
const needed_size = padToIdeal(@sizeOf(macho.linkedit_data_command));
if (needed_size + after_last_cmd_offset > text_section.offset) {
log.err("Unable to extend padding between the end of load commands and start of __text section.", .{});
log.err("Re-run the linker with '-headerpad 0x{x}' option if available, or", .{needed_size});
log.err("fall back to the system linker by exporting 'ZIG_SYSTEM_LINKER_HACK=1'.", .{});
return error.NotEnoughPadding;
}
// Calculate next available dylib ordinal.
const next_ordinal = blk: {
var ordinal: u32 = 1;
for (self.load_commands.items) |cmd| {
switch (cmd) {
.Dylib => ordinal += 1,
else => {},
}
}
break :blk ordinal;
};
// Add load dylib load command
self.libsystem_cmd_index = @intCast(u16, self.load_commands.items.len);
const cmdsize = @intCast(u32, mem.alignForwardGeneric(
u64,
@sizeOf(macho.dylib_command) + mem.lenZ(LIB_SYSTEM_PATH),
@sizeOf(u64),
));
// TODO Find a way to work out runtime version from the OS version triple stored in std.Target.
// In the meantime, we're gonna hardcode to the minimum compatibility version of 0.0.0.
const min_version = 0x0;
var dylib_cmd = emptyGenericCommandWithData(macho.dylib_command{
.cmd = macho.LC_LOAD_DYLIB,
.cmdsize = cmdsize,
.dylib = .{
.name = @sizeOf(macho.dylib_command),
.timestamp = 2, // not sure why not simply 0; this is reverse engineered from Mach-O files
.current_version = min_version,
.compatibility_version = min_version,
},
});
dylib_cmd.data = try self.base.allocator.alloc(u8, cmdsize - dylib_cmd.inner.dylib.name);
mem.set(u8, dylib_cmd.data, 0);
mem.copy(u8, dylib_cmd.data, mem.spanZ(LIB_SYSTEM_PATH));
try self.load_commands.append(self.base.allocator, .{ .Dylib = dylib_cmd });
self.header_dirty = true;
self.load_commands_dirty = true;
if (self.symtab_cmd_index == null or self.dysymtab_cmd_index == null) {
log.err("Incomplete Mach-O binary: no LC_SYMTAB or LC_DYSYMTAB load command found!", .{});
log.err("Without the symbol table, it is not possible to patch up the binary for cross-compilation.", .{});
return error.NoSymbolTableFound;
}
// Patch dyld info
try self.fixupBindInfo(next_ordinal);
try self.fixupLazyBindInfo(next_ordinal);
// Write updated load commands and the header
try self.writeLoadCommands();
try self.writeHeader();
assert(!self.header_dirty);
assert(!self.load_commands_dirty);
}
if (self.code_signature_cmd_index == null) outer: {
if (target.cpu.arch != .aarch64) break :outer; // This is currently needed only for aarch64 targets.
const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment;
const text_section = text_segment.sections.items[self.text_section_index.?];
const after_last_cmd_offset = self.header.?.sizeofcmds + @sizeOf(macho.mach_header_64);
const needed_size = padToIdeal(@sizeOf(macho.linkedit_data_command));
if (needed_size + after_last_cmd_offset > text_section.offset) {
log.err("Unable to extend padding between the end of load commands and start of __text section.", .{});
log.err("Re-run the linker with '-headerpad 0x{x}' option if available, or", .{needed_size});
log.err("fall back to the system linker by exporting 'ZIG_SYSTEM_LINKER_HACK=1'.", .{});
return error.NotEnoughPadding;
}
// Add code signature load command
self.code_signature_cmd_index = @intCast(u16, self.load_commands.items.len);
try self.load_commands.append(self.base.allocator, .{
.LinkeditData = .{
.cmd = macho.LC_CODE_SIGNATURE,
.cmdsize = @sizeOf(macho.linkedit_data_command),
.dataoff = 0,
.datasize = 0,
},
});
self.header_dirty = true;
self.load_commands_dirty = true;
// Pad out space for code signature
try self.writeCodeSignaturePadding();
// Write updated load commands and the header
try self.writeLoadCommands();
try self.writeHeader();
// Generate adhoc code signature
try self.writeCodeSignature();
assert(!self.header_dirty);
assert(!self.load_commands_dirty);
}
}
}
@ -3331,177 +3286,6 @@ fn writeHeader(self: *MachO) !void {
self.header_dirty = false;
}
/// Parse MachO contents from existing binary file.
fn parseFromFile(self: *MachO, file: fs.File) !void {
self.base.file = file;
var reader = file.reader();
const header = try reader.readStruct(macho.mach_header_64);
try self.load_commands.ensureCapacity(self.base.allocator, header.ncmds);
var i: u16 = 0;
while (i < header.ncmds) : (i += 1) {
const cmd = try LoadCommand.read(self.base.allocator, reader);
switch (cmd.cmd()) {
macho.LC_SEGMENT_64 => {
const x = cmd.Segment;
if (parseAndCmpName(&x.inner.segname, "__PAGEZERO")) {
self.pagezero_segment_cmd_index = i;
} else if (parseAndCmpName(&x.inner.segname, "__LINKEDIT")) {
self.linkedit_segment_cmd_index = i;
} else if (parseAndCmpName(&x.inner.segname, "__TEXT")) {
self.text_segment_cmd_index = i;
for (x.sections.items) |sect, j| {
if (parseAndCmpName(&sect.sectname, "__text")) {
self.text_section_index = @intCast(u16, j);
}
}
} else if (parseAndCmpName(&x.inner.segname, "__DATA")) {
self.data_segment_cmd_index = i;
} else if (parseAndCmpName(&x.inner.segname, "__DATA_CONST")) {
self.data_const_segment_cmd_index = i;
}
},
macho.LC_DYLD_INFO_ONLY => {
self.dyld_info_cmd_index = i;
},
macho.LC_SYMTAB => {
self.symtab_cmd_index = i;
},
macho.LC_DYSYMTAB => {
self.dysymtab_cmd_index = i;
},
macho.LC_LOAD_DYLINKER => {
self.dylinker_cmd_index = i;
},
macho.LC_VERSION_MIN_MACOSX, macho.LC_VERSION_MIN_IPHONEOS, macho.LC_VERSION_MIN_WATCHOS, macho.LC_VERSION_MIN_TVOS => {
self.version_min_cmd_index = i;
},
macho.LC_SOURCE_VERSION => {
self.source_version_cmd_index = i;
},
macho.LC_UUID => {
self.uuid_cmd_index = i;
},
macho.LC_MAIN => {
self.main_cmd_index = i;
},
macho.LC_LOAD_DYLIB => {
const x = cmd.Dylib;
if (parseAndCmpName(x.data, mem.spanZ(LIB_SYSTEM_PATH))) {
self.libsystem_cmd_index = i;
}
},
macho.LC_FUNCTION_STARTS => {
self.function_starts_cmd_index = i;
},
macho.LC_DATA_IN_CODE => {
self.data_in_code_cmd_index = i;
},
macho.LC_CODE_SIGNATURE => {
self.code_signature_cmd_index = i;
},
else => {
log.warn("Unknown load command detected: 0x{x}.", .{cmd.cmd()});
},
}
self.load_commands.appendAssumeCapacity(cmd);
}
self.header = header;
}
fn parseAndCmpName(name: []const u8, needle: []const u8) bool {
const len = mem.indexOfScalar(u8, name, @as(u8, 0)) orelse name.len;
return mem.eql(u8, name[0..len], needle);
}
fn parseSymbolTable(self: *MachO) !void {
const symtab = self.load_commands.items[self.symtab_cmd_index.?].Symtab;
const dysymtab = self.load_commands.items[self.dysymtab_cmd_index.?].Dysymtab;
var buffer = try self.base.allocator.alloc(macho.nlist_64, symtab.nsyms);
defer self.base.allocator.free(buffer);
const nread = try self.base.file.?.preadAll(@ptrCast([*]u8, buffer)[0 .. symtab.nsyms * @sizeOf(macho.nlist_64)], symtab.symoff);
assert(@divExact(nread, @sizeOf(macho.nlist_64)) == buffer.len);
try self.locals.ensureCapacity(self.base.allocator, dysymtab.nlocalsym);
try self.globals.ensureCapacity(self.base.allocator, dysymtab.nextdefsym);
try self.undef_symbols.ensureCapacity(self.base.allocator, dysymtab.nundefsym);
self.locals.appendSliceAssumeCapacity(buffer[dysymtab.ilocalsym .. dysymtab.ilocalsym + dysymtab.nlocalsym]);
self.globals.appendSliceAssumeCapacity(buffer[dysymtab.iextdefsym .. dysymtab.iextdefsym + dysymtab.nextdefsym]);
self.undef_symbols.appendSliceAssumeCapacity(buffer[dysymtab.iundefsym .. dysymtab.iundefsym + dysymtab.nundefsym]);
}
fn parseStringTable(self: *MachO) !void {
const symtab = self.load_commands.items[self.symtab_cmd_index.?].Symtab;
var buffer = try self.base.allocator.alloc(u8, symtab.strsize);
defer self.base.allocator.free(buffer);
const nread = try self.base.file.?.preadAll(buffer, symtab.stroff);
assert(nread == buffer.len);
try self.string_table.ensureCapacity(self.base.allocator, symtab.strsize);
self.string_table.appendSliceAssumeCapacity(buffer);
}
fn fixupBindInfo(self: *MachO, dylib_ordinal: u32) !void {
const dyld_info = self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly;
var buffer = try self.base.allocator.alloc(u8, dyld_info.bind_size);
defer self.base.allocator.free(buffer);
const nread = try self.base.file.?.preadAll(buffer, dyld_info.bind_off);
assert(nread == buffer.len);
try self.fixupInfoCommon(buffer, dylib_ordinal);
try self.base.file.?.pwriteAll(buffer, dyld_info.bind_off);
}
fn fixupLazyBindInfo(self: *MachO, dylib_ordinal: u32) !void {
const dyld_info = self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly;
var buffer = try self.base.allocator.alloc(u8, dyld_info.lazy_bind_size);
defer self.base.allocator.free(buffer);
const nread = try self.base.file.?.preadAll(buffer, dyld_info.lazy_bind_off);
assert(nread == buffer.len);
try self.fixupInfoCommon(buffer, dylib_ordinal);
try self.base.file.?.pwriteAll(buffer, dyld_info.lazy_bind_off);
}
fn fixupInfoCommon(self: *MachO, buffer: []u8, dylib_ordinal: u32) !void {
var stream = std.io.fixedBufferStream(buffer);
var reader = stream.reader();
while (true) {
const inst = reader.readByte() catch |err| switch (err) {
error.EndOfStream => break,
else => return err,
};
const imm: u8 = inst & macho.BIND_IMMEDIATE_MASK;
const opcode: u8 = inst & macho.BIND_OPCODE_MASK;
switch (opcode) {
macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM => {
var next = try reader.readByte();
while (next != @as(u8, 0)) {
next = try reader.readByte();
}
},
macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB => {
_ = try std.leb.readULEB128(u64, reader);
},
macho.BIND_OPCODE_SET_DYLIB_SPECIAL_IMM, macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM => {
// Perform the fixup.
try stream.seekBy(-1);
var writer = stream.writer();
try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | @truncate(u4, dylib_ordinal));
},
macho.BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB => {
_ = try std.leb.readULEB128(u64, reader);
},
macho.BIND_OPCODE_SET_ADDEND_SLEB => {
_ = try std.leb.readILEB128(i64, reader);
},
else => {},
}
}
}
pub fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) {
// TODO https://github.com/ziglang/zig/issues/1284
return std.math.add(@TypeOf(actual_size), actual_size, actual_size / ideal_factor) catch

256
src/link/MachO/Archive.zig Normal file
View File

@ -0,0 +1,256 @@
const Archive = @This();
const std = @import("std");
const assert = std.debug.assert;
const fs = std.fs;
const log = std.log.scoped(.archive);
const macho = std.macho;
const mem = std.mem;
const Allocator = mem.Allocator;
const Object = @import("Object.zig");
const parseName = @import("Zld.zig").parseName;
usingnamespace @import("commands.zig");
allocator: *Allocator,
file: fs.File,
header: ar_hdr,
name: []u8,
objects: std.ArrayListUnmanaged(Object) = .{},
// Archive files start with the ARMAG identifying string. Then follows a
// `struct ar_hdr', and as many bytes of member file data as its `ar_size'
// member indicates, for each member file.
/// String that begins an archive file.
const ARMAG: *const [SARMAG:0]u8 = "!<arch>\n";
/// Size of that string.
const SARMAG: u4 = 8;
/// String in ar_fmag at the end of each header.
const ARFMAG: *const [2:0]u8 = "`\n";
const ar_hdr = extern struct {
/// Member file name, sometimes / terminated.
ar_name: [16]u8,
/// File date, decimal seconds since Epoch.
ar_date: [12]u8,
/// User ID, in ASCII format.
ar_uid: [6]u8,
/// Group ID, in ASCII format.
ar_gid: [6]u8,
/// File mode, in ASCII octal.
ar_mode: [8]u8,
/// File size, in ASCII decimal.
ar_size: [10]u8,
/// Always contains ARFMAG.
ar_fmag: [2]u8,
const NameOrLength = union(enum) {
Name: []const u8,
Length: u64,
};
pub fn nameOrLength(self: ar_hdr) !NameOrLength {
const value = getValue(&self.ar_name);
const slash_index = mem.indexOf(u8, value, "/") orelse return error.MalformedArchive;
const len = value.len;
if (slash_index == len - 1) {
// Name stored directly
return NameOrLength{ .Name = value };
} else {
// Name follows the header directly and its length is encoded in
// the name field.
const length = try std.fmt.parseInt(u64, value[slash_index + 1 ..], 10);
return NameOrLength{ .Length = length };
}
}
pub fn size(self: ar_hdr) !u64 {
const value = getValue(&self.ar_size);
return std.fmt.parseInt(u64, value, 10);
}
fn getValue(raw: []const u8) []const u8 {
return mem.trimRight(u8, raw, &[_]u8{@as(u8, 0x20)});
}
};
pub fn deinit(self: *Archive) void {
self.allocator.free(self.name);
for (self.objects.items) |*object| {
object.deinit();
}
self.objects.deinit(self.allocator);
self.file.close();
}
/// Caller owns the returned Archive instance and is responsible for calling
/// `deinit` to free allocated memory.
pub fn initFromFile(allocator: *Allocator, arch: std.Target.Cpu.Arch, ar_name: []const u8, file: fs.File) !Archive {
var reader = file.reader();
var magic = try readMagic(allocator, reader);
defer allocator.free(magic);
if (!mem.eql(u8, magic, ARMAG)) {
// Reset file cursor.
try file.seekTo(0);
return error.NotArchive;
}
const header = try reader.readStruct(ar_hdr);
if (!mem.eql(u8, &header.ar_fmag, ARFMAG))
return error.MalformedArchive;
var embedded_name = try getName(allocator, header, reader);
log.debug("parsing archive '{s}' at '{s}'", .{ embedded_name, ar_name });
defer allocator.free(embedded_name);
var name = try allocator.dupe(u8, ar_name);
var self = Archive{
.allocator = allocator,
.file = file,
.header = header,
.name = name,
};
var object_offsets = try self.readTableOfContents(reader);
defer self.allocator.free(object_offsets);
var i: usize = 1;
while (i < object_offsets.len) : (i += 1) {
const offset = object_offsets[i];
try reader.context.seekTo(offset);
try self.readObject(arch, ar_name, reader);
}
return self;
}
fn readTableOfContents(self: *Archive, reader: anytype) ![]u32 {
const symtab_size = try reader.readIntLittle(u32);
var symtab = try self.allocator.alloc(u8, symtab_size);
defer self.allocator.free(symtab);
try reader.readNoEof(symtab);
const strtab_size = try reader.readIntLittle(u32);
var strtab = try self.allocator.alloc(u8, strtab_size);
defer self.allocator.free(strtab);
try reader.readNoEof(strtab);
var symtab_stream = std.io.fixedBufferStream(symtab);
var symtab_reader = symtab_stream.reader();
var object_offsets = std.ArrayList(u32).init(self.allocator);
try object_offsets.append(0);
var last: usize = 0;
while (true) {
const n_strx = symtab_reader.readIntLittle(u32) catch |err| switch (err) {
error.EndOfStream => break,
else => |e| return e,
};
const object_offset = try symtab_reader.readIntLittle(u32);
// TODO Store the table of contents for later reuse.
// Here, we assume that symbols are NOT sorted in any way, and
// they point to objects in sequence.
if (object_offsets.items[last] != object_offset) {
try object_offsets.append(object_offset);
last += 1;
}
}
return object_offsets.toOwnedSlice();
}
fn readObject(self: *Archive, arch: std.Target.Cpu.Arch, ar_name: []const u8, reader: anytype) !void {
const object_header = try reader.readStruct(ar_hdr);
if (!mem.eql(u8, &object_header.ar_fmag, ARFMAG))
return error.MalformedArchive;
var object_name = try getName(self.allocator, object_header, reader);
log.debug("extracting object '{s}' from archive '{s}'", .{ object_name, self.name });
const offset = @intCast(u32, try reader.context.getPos());
const header = try reader.readStruct(macho.mach_header_64);
const this_arch: std.Target.Cpu.Arch = switch (header.cputype) {
macho.CPU_TYPE_ARM64 => .aarch64,
macho.CPU_TYPE_X86_64 => .x86_64,
else => |value| {
log.err("unsupported cpu architecture 0x{x}", .{value});
return error.UnsupportedCpuArchitecture;
},
};
if (this_arch != arch) {
log.err("mismatched cpu architecture: found {s}, expected {s}", .{ this_arch, arch });
return error.MismatchedCpuArchitecture;
}
// TODO Implement std.fs.File.clone() or similar.
var new_file = try fs.cwd().openFile(ar_name, .{});
var object = Object{
.allocator = self.allocator,
.name = object_name,
.ar_name = try mem.dupe(self.allocator, u8, ar_name),
.file = new_file,
.header = header,
};
try object.readLoadCommands(reader, .{ .offset = offset });
if (object.symtab_cmd_index != null) {
try object.readSymtab();
try object.readStrtab();
}
if (object.data_in_code_cmd_index != null) try object.readDataInCode();
log.debug("\n\n", .{});
log.debug("{s} defines symbols", .{object.name});
for (object.symtab.items) |sym| {
const symname = object.getString(sym.n_strx);
log.debug("'{s}': {}", .{ symname, sym });
}
try self.objects.append(self.allocator, object);
}
fn readMagic(allocator: *Allocator, reader: anytype) ![]u8 {
var magic = std.ArrayList(u8).init(allocator);
try magic.ensureCapacity(SARMAG);
var i: usize = 0;
while (i < SARMAG) : (i += 1) {
const next = try reader.readByte();
magic.appendAssumeCapacity(next);
}
return magic.toOwnedSlice();
}
fn getName(allocator: *Allocator, header: ar_hdr, reader: anytype) ![]u8 {
const name_or_length = try header.nameOrLength();
var name: []u8 = undefined;
switch (name_or_length) {
.Name => |n| {
name = try allocator.dupe(u8, n);
},
.Length => |len| {
var n = try allocator.alloc(u8, len);
defer allocator.free(n);
try reader.readNoEof(n);
const actual_len = mem.indexOfScalar(u8, n, @as(u8, 0));
name = try allocator.dupe(u8, n[0..actual_len.?]);
},
}
return name;
}

228
src/link/MachO/Object.zig Normal file
View File

@ -0,0 +1,228 @@
const Object = @This();
const std = @import("std");
const assert = std.debug.assert;
const fs = std.fs;
const io = std.io;
const log = std.log.scoped(.object);
const macho = std.macho;
const mem = std.mem;
const Allocator = mem.Allocator;
const parseName = @import("Zld.zig").parseName;
usingnamespace @import("commands.zig");
allocator: *Allocator,
file: fs.File,
name: []u8,
ar_name: ?[]u8 = null,
header: macho.mach_header_64,
load_commands: std.ArrayListUnmanaged(LoadCommand) = .{},
segment_cmd_index: ?u16 = null,
symtab_cmd_index: ?u16 = null,
dysymtab_cmd_index: ?u16 = null,
build_version_cmd_index: ?u16 = null,
data_in_code_cmd_index: ?u16 = null,
text_section_index: ?u16 = null,
// __DWARF segment sections
dwarf_debug_info_index: ?u16 = null,
dwarf_debug_abbrev_index: ?u16 = null,
dwarf_debug_str_index: ?u16 = null,
dwarf_debug_line_index: ?u16 = null,
dwarf_debug_ranges_index: ?u16 = null,
symtab: std.ArrayListUnmanaged(macho.nlist_64) = .{},
strtab: std.ArrayListUnmanaged(u8) = .{},
data_in_code_entries: std.ArrayListUnmanaged(macho.data_in_code_entry) = .{},
pub fn deinit(self: *Object) void {
for (self.load_commands.items) |*lc| {
lc.deinit(self.allocator);
}
self.load_commands.deinit(self.allocator);
self.symtab.deinit(self.allocator);
self.strtab.deinit(self.allocator);
self.data_in_code_entries.deinit(self.allocator);
self.allocator.free(self.name);
if (self.ar_name) |v| {
self.allocator.free(v);
}
self.file.close();
}
/// Caller owns the returned Object instance and is responsible for calling
/// `deinit` to free allocated memory.
pub fn initFromFile(allocator: *Allocator, arch: std.Target.Cpu.Arch, name: []const u8, file: fs.File) !Object {
var reader = file.reader();
const header = try reader.readStruct(macho.mach_header_64);
if (header.filetype != macho.MH_OBJECT) {
// Reset file cursor.
try file.seekTo(0);
return error.NotObject;
}
const this_arch: std.Target.Cpu.Arch = switch (header.cputype) {
macho.CPU_TYPE_ARM64 => .aarch64,
macho.CPU_TYPE_X86_64 => .x86_64,
else => |value| {
log.err("unsupported cpu architecture 0x{x}", .{value});
return error.UnsupportedCpuArchitecture;
},
};
if (this_arch != arch) {
log.err("mismatched cpu architecture: found {s}, expected {s}", .{ this_arch, arch });
return error.MismatchedCpuArchitecture;
}
var self = Object{
.allocator = allocator,
.name = try allocator.dupe(u8, name),
.file = file,
.header = header,
};
try self.readLoadCommands(reader, .{});
if (self.symtab_cmd_index != null) {
try self.readSymtab();
try self.readStrtab();
}
if (self.data_in_code_cmd_index != null) try self.readDataInCode();
log.debug("\n\n", .{});
log.debug("{s} defines symbols", .{self.name});
for (self.symtab.items) |sym| {
const symname = self.getString(sym.n_strx);
log.debug("'{s}': {}", .{ symname, sym });
}
return self;
}
pub const ReadOffset = struct {
offset: ?u32 = null,
};
pub fn readLoadCommands(self: *Object, reader: anytype, offset: ReadOffset) !void {
const offset_mod = offset.offset orelse 0;
try self.load_commands.ensureCapacity(self.allocator, self.header.ncmds);
var i: u16 = 0;
while (i < self.header.ncmds) : (i += 1) {
var cmd = try LoadCommand.read(self.allocator, reader);
switch (cmd.cmd()) {
macho.LC_SEGMENT_64 => {
self.segment_cmd_index = i;
var seg = cmd.Segment;
for (seg.sections.items) |*sect, j| {
const index = @intCast(u16, j);
const segname = parseName(&sect.segname);
const sectname = parseName(&sect.sectname);
if (mem.eql(u8, segname, "__DWARF")) {
if (mem.eql(u8, sectname, "__debug_info")) {
self.dwarf_debug_info_index = index;
} else if (mem.eql(u8, sectname, "__debug_abbrev")) {
self.dwarf_debug_abbrev_index = index;
} else if (mem.eql(u8, sectname, "__debug_str")) {
self.dwarf_debug_str_index = index;
} else if (mem.eql(u8, sectname, "__debug_line")) {
self.dwarf_debug_line_index = index;
} else if (mem.eql(u8, sectname, "__debug_ranges")) {
self.dwarf_debug_ranges_index = index;
}
} else if (mem.eql(u8, segname, "__TEXT")) {
if (mem.eql(u8, sectname, "__text")) {
self.text_section_index = index;
}
}
sect.offset += offset_mod;
if (sect.reloff > 0)
sect.reloff += offset_mod;
}
seg.inner.fileoff += offset_mod;
},
macho.LC_SYMTAB => {
self.symtab_cmd_index = i;
cmd.Symtab.symoff += offset_mod;
cmd.Symtab.stroff += offset_mod;
},
macho.LC_DYSYMTAB => {
self.dysymtab_cmd_index = i;
},
macho.LC_BUILD_VERSION => {
self.build_version_cmd_index = i;
},
macho.LC_DATA_IN_CODE => {
self.data_in_code_cmd_index = i;
},
else => {
log.debug("Unknown load command detected: 0x{x}.", .{cmd.cmd()});
},
}
self.load_commands.appendAssumeCapacity(cmd);
}
}
pub fn readSymtab(self: *Object) !void {
const symtab_cmd = self.load_commands.items[self.symtab_cmd_index.?].Symtab;
var buffer = try self.allocator.alloc(u8, @sizeOf(macho.nlist_64) * symtab_cmd.nsyms);
defer self.allocator.free(buffer);
_ = try self.file.preadAll(buffer, symtab_cmd.symoff);
try self.symtab.ensureCapacity(self.allocator, symtab_cmd.nsyms);
// TODO this align case should not be needed.
// Probably a bug in stage1.
const slice = @alignCast(@alignOf(macho.nlist_64), mem.bytesAsSlice(macho.nlist_64, buffer));
self.symtab.appendSliceAssumeCapacity(slice);
}
pub fn readStrtab(self: *Object) !void {
const symtab_cmd = self.load_commands.items[self.symtab_cmd_index.?].Symtab;
var buffer = try self.allocator.alloc(u8, symtab_cmd.strsize);
defer self.allocator.free(buffer);
_ = try self.file.preadAll(buffer, symtab_cmd.stroff);
try self.strtab.ensureCapacity(self.allocator, symtab_cmd.strsize);
self.strtab.appendSliceAssumeCapacity(buffer);
}
pub fn getString(self: *const Object, str_off: u32) []const u8 {
assert(str_off < self.strtab.items.len);
return mem.spanZ(@ptrCast([*:0]const u8, self.strtab.items.ptr + str_off));
}
pub fn readSection(self: Object, allocator: *Allocator, index: u16) ![]u8 {
const seg = self.load_commands.items[self.segment_cmd_index.?].Segment;
const sect = seg.sections.items[index];
var buffer = try allocator.alloc(u8, sect.size);
_ = try self.file.preadAll(buffer, sect.offset);
return buffer;
}
pub fn readDataInCode(self: *Object) !void {
const index = self.data_in_code_cmd_index orelse return;
const data_in_code = self.load_commands.items[index].LinkeditData;
var buffer = try self.allocator.alloc(u8, data_in_code.datasize);
defer self.allocator.free(buffer);
_ = try self.file.preadAll(buffer, data_in_code.dataoff);
var stream = io.fixedBufferStream(buffer);
var reader = stream.reader();
while (true) {
const dice = reader.readStruct(macho.data_in_code_entry) catch |err| switch (err) {
error.EndOfStream => break,
else => |e| return e,
};
try self.data_in_code_entries.append(self.allocator, dice);
}
}

3192
src/link/MachO/Zld.zig Normal file

File diff suppressed because it is too large Load Diff

View File

@ -3281,7 +3281,8 @@ pub const ClangArgIterator = struct {
self.zig_equivalent = clang_arg.zig_equivalent;
break :find_clang_arg;
},
} else {
}
else {
fatal("Unknown Clang option: '{s}'", .{arg});
}
}

View File

@ -9,7 +9,10 @@ pub fn addCases(cases: *tests.StandaloneContext) void {
cases.add("test/standalone/main_return_error/error_u8.zig");
cases.add("test/standalone/main_return_error/error_u8_non_zero.zig");
cases.addBuildFile("test/standalone/main_pkg_path/build.zig");
cases.addBuildFile("test/standalone/shared_library/build.zig");
if (std.Target.current.os.tag != .macos) {
// TODO zld cannot link shared libraries yet.
cases.addBuildFile("test/standalone/shared_library/build.zig");
}
cases.addBuildFile("test/standalone/mix_o_files/build.zig");
cases.addBuildFile("test/standalone/global_linkage/build.zig");
cases.addBuildFile("test/standalone/static_c_lib/build.zig");