Merge pull request #9229 from ziglang/zld-objc-frameworks

zig ld: link Obj-C, link frameworks, improve linker's implementation
This commit is contained in:
Jakub Konka 2021-06-25 07:51:21 +02:00 committed by GitHub
commit 350ead9cb2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 1108 additions and 1259 deletions

View File

@ -579,7 +579,6 @@ set(ZIG_STAGE2_SOURCES
"${CMAKE_SOURCE_DIR}/src/link/MachO/DebugSymbols.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/Dylib.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/Object.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/Stub.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/Symbol.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/Trie.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/Zld.zig"

View File

@ -2857,7 +2857,7 @@ pub fn addCCArgs(
try argv.appendSlice(&[_][]const u8{ "-target", llvm_triple });
switch (ext) {
.c, .cpp, .h => {
.c, .cpp, .m, .h => {
try argv.appendSlice(&[_][]const u8{
"-nostdinc",
"-fno-spell-checking",
@ -3148,6 +3148,7 @@ pub const FileExt = enum {
c,
cpp,
h,
m,
ll,
bc,
assembly,
@ -3159,7 +3160,7 @@ pub const FileExt = enum {
pub fn clangSupportsDepFile(ext: FileExt) bool {
return switch (ext) {
.c, .cpp, .h => true,
.c, .cpp, .h, .m => true,
.ll,
.bc,
@ -3193,6 +3194,10 @@ pub fn hasCppExt(filename: []const u8) bool {
mem.endsWith(u8, filename, ".cxx");
}
pub fn hasObjCExt(filename: []const u8) bool {
return mem.endsWith(u8, filename, ".m");
}
pub fn hasAsmExt(filename: []const u8) bool {
return mem.endsWith(u8, filename, ".s") or mem.endsWith(u8, filename, ".S");
}
@ -3229,6 +3234,8 @@ pub fn classifyFileExt(filename: []const u8) FileExt {
return .c;
} else if (hasCppExt(filename)) {
return .cpp;
} else if (hasObjCExt(filename)) {
return .m;
} else if (mem.endsWith(u8, filename, ".ll")) {
return .ll;
} else if (mem.endsWith(u8, filename, ".bc")) {
@ -3252,6 +3259,7 @@ pub fn classifyFileExt(filename: []const u8) FileExt {
test "classifyFileExt" {
try std.testing.expectEqual(FileExt.cpp, classifyFileExt("foo.cc"));
try std.testing.expectEqual(FileExt.m, classifyFileExt("foo.m"));
try std.testing.expectEqual(FileExt.unknown, classifyFileExt("foo.nim"));
try std.testing.expectEqual(FileExt.shared_library, classifyFileExt("foo.so"));
try std.testing.expectEqual(FileExt.shared_library, classifyFileExt("foo.so.1"));

View File

@ -514,6 +514,119 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void {
}
}
fn resolvePaths(
arena: *Allocator,
resolved_paths: *std.ArrayList([]const u8),
syslibroot: ?[]const u8,
search_dirs: []const []const u8,
lib_names: []const []const u8,
kind: enum { lib, framework },
) !void {
var resolved_dirs = std.ArrayList([]const u8).init(arena);
for (search_dirs) |dir| {
if (fs.path.isAbsolute(dir)) {
var candidates = std.ArrayList([]const u8).init(arena);
if (syslibroot) |root| {
const full_path = try fs.path.join(arena, &[_][]const u8{ root, dir });
try candidates.append(full_path);
}
try candidates.append(dir);
var found = false;
for (candidates.items) |candidate| {
// Verify that search path actually exists
var tmp = fs.cwd().openDir(candidate, .{}) catch |err| switch (err) {
error.FileNotFound => continue,
else => |e| return e,
};
defer tmp.close();
try resolved_dirs.append(candidate);
found = true;
break;
}
if (!found) {
switch (kind) {
.lib => log.warn("directory not found for '-L{s}'", .{dir}),
.framework => log.warn("directory not found for '-F{s}'", .{dir}),
}
}
} else {
// Verify that search path actually exists
var tmp = fs.cwd().openDir(dir, .{}) catch |err| switch (err) {
error.FileNotFound => {
switch (kind) {
.lib => log.warn("directory not found for '-L{s}'", .{dir}),
.framework => log.warn("directory not found for '-F{s}'", .{dir}),
}
continue;
},
else => |e| return e,
};
defer tmp.close();
try resolved_dirs.append(dir);
}
}
// Assume ld64 default: -search_paths_first
// Look in each directory for a dylib (next, tbd), and then for archive
// TODO implement alternative: -search_dylibs_first
const exts = switch (kind) {
.lib => &[_][]const u8{ "dylib", "tbd", "a" },
.framework => &[_][]const u8{ "dylib", "tbd" },
};
for (lib_names) |lib_name| {
var found = false;
ext: for (exts) |ext| {
const lib_name_ext = blk: {
switch (kind) {
.lib => break :blk try std.fmt.allocPrint(arena, "lib{s}.{s}", .{ lib_name, ext }),
.framework => {
const prefix = try std.fmt.allocPrint(arena, "{s}.framework", .{lib_name});
const nn = try std.fmt.allocPrint(arena, "{s}.{s}", .{ lib_name, ext });
break :blk try fs.path.join(arena, &[_][]const u8{ prefix, nn });
},
}
};
for (resolved_dirs.items) |dir| {
const full_path = try fs.path.join(arena, &[_][]const u8{ dir, lib_name_ext });
// Check if the lib file exists.
const tmp = fs.cwd().openFile(full_path, .{}) catch |err| switch (err) {
error.FileNotFound => continue,
else => |e| return e,
};
defer tmp.close();
try resolved_paths.append(full_path);
found = true;
break :ext;
}
}
if (!found) {
switch (kind) {
.lib => {
log.warn("library not found for '-l{s}'", .{lib_name});
log.warn("Library search paths:", .{});
},
.framework => {
log.warn("framework not found for '-f{s}'", .{lib_name});
log.warn("Framework search paths:", .{});
},
}
for (resolved_dirs.items) |dir| {
log.warn(" {s}", .{dir});
}
}
}
}
fn linkWithLLD(self: *MachO, comp: *Compilation) !void {
const tracy = trace(@src());
defer tracy.end();
@ -676,6 +789,7 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void {
zld.deinit();
}
zld.arch = target.cpu.arch;
zld.syslibroot = self.base.options.syslibroot;
zld.stack_size = stack_size;
// Positional arguments to the linker such as object files and static archives.
@ -700,7 +814,6 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void {
}
// Shared and static libraries passed via `-l` flag.
var libs = std.ArrayList([]const u8).init(arena);
var search_lib_names = std.ArrayList([]const u8).init(arena);
const system_libs = self.base.options.system_libs.keys();
@ -716,84 +829,15 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void {
try search_lib_names.append(link_lib);
}
var search_lib_dirs = std.ArrayList([]const u8).init(arena);
for (self.base.options.lib_dirs) |path| {
if (fs.path.isAbsolute(path)) {
var candidates = std.ArrayList([]const u8).init(arena);
if (self.base.options.syslibroot) |syslibroot| {
const full_path = try fs.path.join(arena, &[_][]const u8{ syslibroot, path });
try candidates.append(full_path);
}
try candidates.append(path);
var found = false;
for (candidates.items) |candidate| {
// Verify that search path actually exists
var tmp = fs.cwd().openDir(candidate, .{}) catch |err| switch (err) {
error.FileNotFound => continue,
else => |e| return e,
};
defer tmp.close();
try search_lib_dirs.append(candidate);
found = true;
break;
}
if (!found) {
log.warn("directory not found for '-L{s}'", .{path});
}
} else {
// Verify that search path actually exists
var tmp = fs.cwd().openDir(path, .{}) catch |err| switch (err) {
error.FileNotFound => {
log.warn("directory not found for '-L{s}'", .{path});
continue;
},
else => |e| return e,
};
defer tmp.close();
try search_lib_dirs.append(path);
}
}
// Assume ld64 default: -search_paths_first
// Look in each directory for a dylib (next, tbd), and then for archive
// TODO implement alternative: -search_dylibs_first
const exts = &[_][]const u8{ "dylib", "tbd", "a" };
for (search_lib_names.items) |l_name| {
var found = false;
ext: for (exts) |ext| {
const l_name_ext = try std.fmt.allocPrint(arena, "lib{s}.{s}", .{ l_name, ext });
for (search_lib_dirs.items) |lib_dir| {
const full_path = try fs.path.join(arena, &[_][]const u8{ lib_dir, l_name_ext });
// Check if the lib file exists.
const tmp = fs.cwd().openFile(full_path, .{}) catch |err| switch (err) {
error.FileNotFound => continue,
else => |e| return e,
};
defer tmp.close();
try libs.append(full_path);
found = true;
break :ext;
}
}
if (!found) {
log.warn("library not found for '-l{s}'", .{l_name});
log.warn("Library search paths:", .{});
for (search_lib_dirs.items) |lib_dir| {
log.warn(" {s}", .{lib_dir});
}
}
}
var libs = std.ArrayList([]const u8).init(arena);
try resolvePaths(
arena,
&libs,
self.base.options.syslibroot,
self.base.options.lib_dirs,
search_lib_names.items,
.lib,
);
// rpaths
var rpath_table = std.StringArrayHashMap(void).init(arena);
@ -809,9 +853,14 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void {
}
// frameworks
for (self.base.options.frameworks) |framework| {
log.warn("frameworks not yet supported for '-framework {s}'", .{framework});
}
try resolvePaths(
arena,
&libs,
self.base.options.syslibroot,
self.base.options.framework_dirs,
self.base.options.frameworks,
.framework,
);
if (self.base.options.verbose_link) {
var argv = std.ArrayList([]const u8).init(arena);
@ -1731,18 +1780,8 @@ pub fn populateMissingMetadata(self: *MachO) !void {
if (self.pagezero_segment_cmd_index == null) {
self.pagezero_segment_cmd_index = @intCast(u16, self.load_commands.items.len);
try self.load_commands.append(self.base.allocator, .{
.Segment = SegmentCommand.empty(.{
.cmd = macho.LC_SEGMENT_64,
.cmdsize = @sizeOf(macho.segment_command_64),
.segname = makeStaticString("__PAGEZERO"),
.vmaddr = 0,
.Segment = SegmentCommand.empty("__PAGEZERO", .{
.vmsize = 0x100000000, // size always set to 4GB
.fileoff = 0,
.filesize = 0,
.maxprot = 0,
.initprot = 0,
.nsects = 0,
.flags = 0,
}),
});
self.header_dirty = true;
@ -1761,18 +1800,12 @@ pub fn populateMissingMetadata(self: *MachO) !void {
log.debug("found __TEXT segment free space 0x{x} to 0x{x}", .{ 0, needed_size });
try self.load_commands.append(self.base.allocator, .{
.Segment = SegmentCommand.empty(.{
.cmd = macho.LC_SEGMENT_64,
.cmdsize = @sizeOf(macho.segment_command_64),
.segname = makeStaticString("__TEXT"),
.Segment = SegmentCommand.empty("__TEXT", .{
.vmaddr = 0x100000000, // always starts at 4GB
.vmsize = needed_size,
.fileoff = 0,
.filesize = needed_size,
.maxprot = maxprot,
.initprot = initprot,
.nsects = 0,
.flags = 0,
}),
});
self.header_dirty = true;
@ -1793,19 +1826,12 @@ pub fn populateMissingMetadata(self: *MachO) !void {
log.debug("found __text section free space 0x{x} to 0x{x}", .{ off, off + needed_size });
try text_segment.addSection(self.base.allocator, .{
.sectname = makeStaticString("__text"),
.segname = makeStaticString("__TEXT"),
try text_segment.addSection(self.base.allocator, "__text", .{
.addr = text_segment.inner.vmaddr + off,
.size = @intCast(u32, needed_size),
.offset = @intCast(u32, off),
.@"align" = alignment,
.reloff = 0,
.nreloc = 0,
.flags = flags,
.reserved1 = 0,
.reserved2 = 0,
.reserved3 = 0,
});
self.header_dirty = true;
self.load_commands_dirty = true;
@ -1831,19 +1857,13 @@ pub fn populateMissingMetadata(self: *MachO) !void {
log.debug("found __stubs section free space 0x{x} to 0x{x}", .{ off, off + needed_size });
try text_segment.addSection(self.base.allocator, .{
.sectname = makeStaticString("__stubs"),
.segname = makeStaticString("__TEXT"),
try text_segment.addSection(self.base.allocator, "__stubs", .{
.addr = text_segment.inner.vmaddr + off,
.size = needed_size,
.offset = @intCast(u32, off),
.@"align" = alignment,
.reloff = 0,
.nreloc = 0,
.flags = flags,
.reserved1 = 0,
.reserved2 = stub_size,
.reserved3 = 0,
});
self.header_dirty = true;
self.load_commands_dirty = true;
@ -1864,19 +1884,12 @@ pub fn populateMissingMetadata(self: *MachO) !void {
log.debug("found __stub_helper section free space 0x{x} to 0x{x}", .{ off, off + needed_size });
try text_segment.addSection(self.base.allocator, .{
.sectname = makeStaticString("__stub_helper"),
.segname = makeStaticString("__TEXT"),
try text_segment.addSection(self.base.allocator, "__stub_helper", .{
.addr = text_segment.inner.vmaddr + off,
.size = needed_size,
.offset = @intCast(u32, off),
.@"align" = alignment,
.reloff = 0,
.nreloc = 0,
.flags = flags,
.reserved1 = 0,
.reserved2 = 0,
.reserved3 = 0,
});
self.header_dirty = true;
self.load_commands_dirty = true;
@ -1893,18 +1906,13 @@ pub fn populateMissingMetadata(self: *MachO) !void {
log.debug("found __DATA_CONST segment free space 0x{x} to 0x{x}", .{ address_and_offset.offset, address_and_offset.offset + needed_size });
try self.load_commands.append(self.base.allocator, .{
.Segment = SegmentCommand.empty(.{
.cmd = macho.LC_SEGMENT_64,
.cmdsize = @sizeOf(macho.segment_command_64),
.segname = makeStaticString("__DATA_CONST"),
.Segment = SegmentCommand.empty("__DATA_CONST", .{
.vmaddr = address_and_offset.address,
.vmsize = needed_size,
.fileoff = address_and_offset.offset,
.filesize = needed_size,
.maxprot = maxprot,
.initprot = initprot,
.nsects = 0,
.flags = 0,
}),
});
self.header_dirty = true;
@ -1921,19 +1929,12 @@ pub fn populateMissingMetadata(self: *MachO) !void {
log.debug("found __got section free space 0x{x} to 0x{x}", .{ off, off + needed_size });
try dc_segment.addSection(self.base.allocator, .{
.sectname = makeStaticString("__got"),
.segname = makeStaticString("__DATA_CONST"),
try dc_segment.addSection(self.base.allocator, "__got", .{
.addr = dc_segment.inner.vmaddr + off - dc_segment.inner.fileoff,
.size = needed_size,
.offset = @intCast(u32, off),
.@"align" = 3, // 2^3 = @sizeOf(u64)
.reloff = 0,
.nreloc = 0,
.flags = flags,
.reserved1 = 0,
.reserved2 = 0,
.reserved3 = 0,
});
self.header_dirty = true;
self.load_commands_dirty = true;
@ -1950,18 +1951,13 @@ pub fn populateMissingMetadata(self: *MachO) !void {
log.debug("found __DATA segment free space 0x{x} to 0x{x}", .{ address_and_offset.offset, address_and_offset.offset + needed_size });
try self.load_commands.append(self.base.allocator, .{
.Segment = SegmentCommand.empty(.{
.cmd = macho.LC_SEGMENT_64,
.cmdsize = @sizeOf(macho.segment_command_64),
.segname = makeStaticString("__DATA"),
.Segment = SegmentCommand.empty("__DATA", .{
.vmaddr = address_and_offset.address,
.vmsize = needed_size,
.fileoff = address_and_offset.offset,
.filesize = needed_size,
.maxprot = maxprot,
.initprot = initprot,
.nsects = 0,
.flags = 0,
}),
});
self.header_dirty = true;
@ -1978,19 +1974,12 @@ pub fn populateMissingMetadata(self: *MachO) !void {
log.debug("found __la_symbol_ptr section free space 0x{x} to 0x{x}", .{ off, off + needed_size });
try data_segment.addSection(self.base.allocator, .{
.sectname = makeStaticString("__la_symbol_ptr"),
.segname = makeStaticString("__DATA"),
try data_segment.addSection(self.base.allocator, "__la_symbol_ptr", .{
.addr = data_segment.inner.vmaddr + off - data_segment.inner.fileoff,
.size = needed_size,
.offset = @intCast(u32, off),
.@"align" = 3, // 2^3 = @sizeOf(u64)
.reloff = 0,
.nreloc = 0,
.flags = flags,
.reserved1 = 0,
.reserved2 = 0,
.reserved3 = 0,
});
self.header_dirty = true;
self.load_commands_dirty = true;
@ -1999,26 +1988,17 @@ pub fn populateMissingMetadata(self: *MachO) !void {
const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
self.data_section_index = @intCast(u16, data_segment.sections.items.len);
const flags = macho.S_REGULAR;
const needed_size = @sizeOf(u64) * self.base.options.symbol_count_hint;
const off = data_segment.findFreeSpace(needed_size, @alignOf(u64), null);
assert(off + needed_size <= data_segment.inner.fileoff + data_segment.inner.filesize); // TODO Must expand __DATA segment.
log.debug("found __data section free space 0x{x} to 0x{x}", .{ off, off + needed_size });
try data_segment.addSection(self.base.allocator, .{
.sectname = makeStaticString("__data"),
.segname = makeStaticString("__DATA"),
try data_segment.addSection(self.base.allocator, "__data", .{
.addr = data_segment.inner.vmaddr + off - data_segment.inner.fileoff,
.size = needed_size,
.offset = @intCast(u32, off),
.@"align" = 3, // 2^3 = @sizeOf(u64)
.reloff = 0,
.nreloc = 0,
.flags = flags,
.reserved1 = 0,
.reserved2 = 0,
.reserved3 = 0,
});
self.header_dirty = true;
self.load_commands_dirty = true;
@ -2033,18 +2013,11 @@ pub fn populateMissingMetadata(self: *MachO) !void {
log.debug("found __LINKEDIT segment free space at 0x{x}", .{address_and_offset.offset});
try self.load_commands.append(self.base.allocator, .{
.Segment = SegmentCommand.empty(.{
.cmd = macho.LC_SEGMENT_64,
.cmdsize = @sizeOf(macho.segment_command_64),
.segname = makeStaticString("__LINKEDIT"),
.Segment = SegmentCommand.empty("__LINKEDIT", .{
.vmaddr = address_and_offset.address,
.vmsize = 0,
.fileoff = address_and_offset.offset,
.filesize = 0,
.maxprot = maxprot,
.initprot = initprot,
.nsects = 0,
.flags = 0,
}),
});
self.header_dirty = true;
@ -2402,13 +2375,6 @@ fn allocateTextBlock(self: *MachO, text_block: *TextBlock, new_block_size: u64,
return vaddr;
}
pub fn makeStaticString(comptime bytes: []const u8) [16]u8 {
var buf = [_]u8{0} ** 16;
if (bytes.len > buf.len) @compileError("string too long; max 16 bytes");
mem.copy(u8, &buf, bytes);
return buf;
}
fn makeString(self: *MachO, bytes: []const u8) !u32 {
if (self.string_table_directory.get(bytes)) |offset| {
log.debug("reusing '{s}' from string table at offset 0x{x}", .{ bytes, offset });

View File

@ -8,12 +8,13 @@ const macho = std.macho;
const mem = std.mem;
const Allocator = mem.Allocator;
const Arch = std.Target.Cpu.Arch;
const Object = @import("Object.zig");
usingnamespace @import("commands.zig");
allocator: *Allocator,
arch: ?std.Target.Cpu.Arch = null,
arch: ?Arch = null,
file: ?fs.File = null,
header: ?ar_hdr = null,
name: ?[]const u8 = null,
@ -85,10 +86,36 @@ const ar_hdr = extern struct {
}
};
pub fn init(allocator: *Allocator) Archive {
return .{
.allocator = allocator,
pub fn createAndParseFromPath(allocator: *Allocator, arch: Arch, path: []const u8) !?*Archive {
const file = fs.cwd().openFile(path, .{}) catch |err| switch (err) {
error.FileNotFound => return null,
else => |e| return e,
};
errdefer file.close();
const archive = try allocator.create(Archive);
errdefer allocator.destroy(archive);
const name = try allocator.dupe(u8, path);
errdefer allocator.free(name);
archive.* = .{
.allocator = allocator,
.arch = arch,
.name = name,
.file = file,
};
archive.parse() catch |err| switch (err) {
error.EndOfStream, error.NotArchive => {
archive.deinit();
allocator.destroy(archive);
return null;
},
else => |e| return e,
};
return archive;
}
pub fn deinit(self: *Archive) void {
@ -116,15 +143,15 @@ pub fn parse(self: *Archive) !void {
const magic = try reader.readBytesNoEof(SARMAG);
if (!mem.eql(u8, &magic, ARMAG)) {
log.err("invalid magic: expected '{s}', found '{s}'", .{ ARMAG, magic });
return error.MalformedArchive;
log.debug("invalid magic: expected '{s}', found '{s}'", .{ ARMAG, magic });
return error.NotArchive;
}
self.header = try reader.readStruct(ar_hdr);
if (!mem.eql(u8, &self.header.?.ar_fmag, ARFMAG)) {
log.err("invalid header delimiter: expected '{s}', found '{s}'", .{ ARFMAG, self.header.?.ar_fmag });
return error.MalformedArchive;
log.debug("invalid header delimiter: expected '{s}', found '{s}'", .{ ARFMAG, self.header.?.ar_fmag });
return error.NotArchive;
}
var embedded_name = try parseName(self.allocator, self.header.?, reader);
@ -222,23 +249,15 @@ pub fn parseObject(self: Archive, offset: u32) !*Object {
var object = try self.allocator.create(Object);
errdefer self.allocator.destroy(object);
object.* = Object.init(self.allocator);
object.arch = self.arch.?;
object.file = try fs.cwd().openFile(self.name.?, .{});
object.name = name;
object.file_offset = @intCast(u32, try reader.context.getPos());
object.* = .{
.allocator = self.allocator,
.arch = self.arch.?,
.file = try fs.cwd().openFile(self.name.?, .{}),
.name = name,
.file_offset = @intCast(u32, try reader.context.getPos()),
};
try object.parse();
try reader.context.seekTo(0);
return object;
}
pub fn isArchive(file: fs.File) !bool {
const magic = file.reader().readBytesNoEof(Archive.SARMAG) catch |err| switch (err) {
error.EndOfStream => return false,
else => |e| return e,
};
try file.seekTo(0);
return mem.eql(u8, &magic, Archive.ARMAG);
}

View File

@ -19,7 +19,6 @@ const MachO = @import("../MachO.zig");
const SrcFn = MachO.SrcFn;
const TextBlock = MachO.TextBlock;
const padToIdeal = MachO.padToIdeal;
const makeStaticString = MachO.makeStaticString;
usingnamespace @import("commands.zig");
@ -212,18 +211,11 @@ pub fn populateMissingMetadata(self: *DebugSymbols, allocator: *Allocator) !void
log.debug("found dSym __DWARF segment free space 0x{x} to 0x{x}", .{ off, off + needed_size });
try self.load_commands.append(allocator, .{
.Segment = SegmentCommand.empty(.{
.cmd = macho.LC_SEGMENT_64,
.cmdsize = @sizeOf(macho.segment_command_64),
.segname = makeStaticString("__DWARF"),
.Segment = SegmentCommand.empty("__DWARF", .{
.vmaddr = vmaddr,
.vmsize = needed_size,
.fileoff = off,
.filesize = needed_size,
.maxprot = 0,
.initprot = 0,
.nsects = 0,
.flags = 0,
}),
});
self.header_dirty = true;
@ -234,19 +226,11 @@ pub fn populateMissingMetadata(self: *DebugSymbols, allocator: *Allocator) !void
self.debug_str_section_index = @intCast(u16, dwarf_segment.sections.items.len);
assert(self.debug_string_table.items.len == 0);
try dwarf_segment.addSection(allocator, .{
.sectname = makeStaticString("__debug_str"),
.segname = makeStaticString("__DWARF"),
try dwarf_segment.addSection(allocator, "__debug_str", .{
.addr = dwarf_segment.inner.vmaddr,
.size = @intCast(u32, self.debug_string_table.items.len),
.offset = @intCast(u32, dwarf_segment.inner.fileoff),
.@"align" = 1,
.reloff = 0,
.nreloc = 0,
.flags = macho.S_REGULAR,
.reserved1 = 0,
.reserved2 = 0,
.reserved3 = 0,
});
self.header_dirty = true;
self.load_commands_dirty = true;
@ -262,19 +246,11 @@ pub fn populateMissingMetadata(self: *DebugSymbols, allocator: *Allocator) !void
log.debug("found dSym __debug_info free space 0x{x} to 0x{x}", .{ off, off + file_size_hint });
try dwarf_segment.addSection(allocator, .{
.sectname = makeStaticString("__debug_info"),
.segname = makeStaticString("__DWARF"),
try dwarf_segment.addSection(allocator, "__debug_info", .{
.addr = dwarf_segment.inner.vmaddr + off - dwarf_segment.inner.fileoff,
.size = file_size_hint,
.offset = @intCast(u32, off),
.@"align" = p_align,
.reloff = 0,
.nreloc = 0,
.flags = macho.S_REGULAR,
.reserved1 = 0,
.reserved2 = 0,
.reserved3 = 0,
});
self.header_dirty = true;
self.load_commands_dirty = true;
@ -290,19 +266,11 @@ pub fn populateMissingMetadata(self: *DebugSymbols, allocator: *Allocator) !void
log.debug("found dSym __debug_abbrev free space 0x{x} to 0x{x}", .{ off, off + file_size_hint });
try dwarf_segment.addSection(allocator, .{
.sectname = makeStaticString("__debug_abbrev"),
.segname = makeStaticString("__DWARF"),
try dwarf_segment.addSection(allocator, "__debug_abbrev", .{
.addr = dwarf_segment.inner.vmaddr + off - dwarf_segment.inner.fileoff,
.size = file_size_hint,
.offset = @intCast(u32, off),
.@"align" = p_align,
.reloff = 0,
.nreloc = 0,
.flags = macho.S_REGULAR,
.reserved1 = 0,
.reserved2 = 0,
.reserved3 = 0,
});
self.header_dirty = true;
self.load_commands_dirty = true;
@ -318,19 +286,11 @@ pub fn populateMissingMetadata(self: *DebugSymbols, allocator: *Allocator) !void
log.debug("found dSym __debug_aranges free space 0x{x} to 0x{x}", .{ off, off + file_size_hint });
try dwarf_segment.addSection(allocator, .{
.sectname = makeStaticString("__debug_aranges"),
.segname = makeStaticString("__DWARF"),
try dwarf_segment.addSection(allocator, "__debug_aranges", .{
.addr = dwarf_segment.inner.vmaddr + off - dwarf_segment.inner.fileoff,
.size = file_size_hint,
.offset = @intCast(u32, off),
.@"align" = p_align,
.reloff = 0,
.nreloc = 0,
.flags = macho.S_REGULAR,
.reserved1 = 0,
.reserved2 = 0,
.reserved3 = 0,
});
self.header_dirty = true;
self.load_commands_dirty = true;
@ -346,19 +306,11 @@ pub fn populateMissingMetadata(self: *DebugSymbols, allocator: *Allocator) !void
log.debug("found dSym __debug_line free space 0x{x} to 0x{x}", .{ off, off + file_size_hint });
try dwarf_segment.addSection(allocator, .{
.sectname = makeStaticString("__debug_line"),
.segname = makeStaticString("__DWARF"),
try dwarf_segment.addSection(allocator, "__debug_line", .{
.addr = dwarf_segment.inner.vmaddr + off - dwarf_segment.inner.fileoff,
.size = file_size_hint,
.offset = @intCast(u32, off),
.@"align" = p_align,
.reloff = 0,
.nreloc = 0,
.flags = macho.S_REGULAR,
.reserved1 = 0,
.reserved2 = 0,
.reserved3 = 0,
});
self.header_dirty = true;
self.load_commands_dirty = true;
@ -692,14 +644,10 @@ pub fn deinit(self: *DebugSymbols, allocator: *Allocator) void {
}
fn copySegmentCommand(self: *DebugSymbols, allocator: *Allocator, base_cmd: SegmentCommand) !SegmentCommand {
var cmd = SegmentCommand.empty(.{
.cmd = macho.LC_SEGMENT_64,
var cmd = SegmentCommand.empty("", .{
.cmdsize = base_cmd.inner.cmdsize,
.segname = undefined,
.vmaddr = base_cmd.inner.vmaddr,
.vmsize = base_cmd.inner.vmsize,
.fileoff = 0,
.filesize = 0,
.maxprot = base_cmd.inner.maxprot,
.initprot = base_cmd.inner.initprot,
.nsects = base_cmd.inner.nsects,

View File

@ -3,20 +3,26 @@ const Dylib = @This();
const std = @import("std");
const assert = std.debug.assert;
const fs = std.fs;
const fmt = std.fmt;
const log = std.log.scoped(.dylib);
const macho = std.macho;
const math = std.math;
const mem = std.mem;
const Allocator = mem.Allocator;
const Arch = std.Target.Cpu.Arch;
const Symbol = @import("Symbol.zig");
const LibStub = @import("../tapi.zig").LibStub;
usingnamespace @import("commands.zig");
allocator: *Allocator,
arch: ?std.Target.Cpu.Arch = null,
arch: ?Arch = null,
header: ?macho.mach_header_64 = null,
file: ?fs.File = null,
name: ?[]const u8 = null,
syslibroot: ?[]const u8 = null,
ordinal: ?u16 = null,
@ -33,19 +39,139 @@ id: ?Id = null,
/// a symbol is referenced by an object file.
symbols: std.StringArrayHashMapUnmanaged(void) = .{},
// TODO add parsing re-exported libs from binary dylibs
dependent_libs: std.StringArrayHashMapUnmanaged(void) = .{},
pub const Id = struct {
name: []const u8,
timestamp: u32,
current_version: u32,
compatibility_version: u32,
pub fn default(name: []const u8) Id {
return .{
.name = name,
.timestamp = 2,
.current_version = 0x10000,
.compatibility_version = 0x10000,
};
}
pub fn deinit(id: *Id, allocator: *Allocator) void {
allocator.free(id.name);
}
const ParseError = fmt.ParseIntError || fmt.BufPrintError;
pub fn parseCurrentVersion(id: *Id, version: anytype) ParseError!void {
id.current_version = try parseVersion(version);
}
pub fn parseCompatibilityVersion(id: *Id, version: anytype) ParseError!void {
id.compatibility_version = try parseVersion(version);
}
fn parseVersion(version: anytype) ParseError!u32 {
const string = blk: {
switch (version) {
.int => |int| {
var out: u32 = 0;
const major = try math.cast(u16, int);
out += @intCast(u32, major) << 16;
return out;
},
.float => |float| {
var buf: [256]u8 = undefined;
break :blk try fmt.bufPrint(&buf, "{d:.2}", .{float});
},
.string => |string| {
break :blk string;
},
}
};
var out: u32 = 0;
var values: [3][]const u8 = undefined;
var split = mem.split(string, ".");
var count: u4 = 0;
while (split.next()) |value| {
if (count > 2) {
log.warn("malformed version field: {s}", .{string});
return 0x10000;
}
values[count] = value;
count += 1;
}
if (count > 2) {
out += try fmt.parseInt(u8, values[2], 10);
}
if (count > 1) {
out += @intCast(u32, try fmt.parseInt(u8, values[1], 10)) << 8;
}
out += @intCast(u32, try fmt.parseInt(u16, values[0], 10)) << 16;
return out;
}
};
pub fn init(allocator: *Allocator) Dylib {
return .{ .allocator = allocator };
pub const Error = error{
OutOfMemory,
EmptyStubFile,
MismatchedCpuArchitecture,
UnsupportedCpuArchitecture,
} || fs.File.OpenError || std.os.PReadError || Id.ParseError;
pub fn createAndParseFromPath(
allocator: *Allocator,
arch: Arch,
path: []const u8,
syslibroot: ?[]const u8,
) Error!?[]*Dylib {
const file = fs.cwd().openFile(path, .{}) catch |err| switch (err) {
error.FileNotFound => return null,
else => |e| return e,
};
errdefer file.close();
const dylib = try allocator.create(Dylib);
errdefer allocator.destroy(dylib);
const name = try allocator.dupe(u8, path);
errdefer allocator.free(name);
dylib.* = .{
.allocator = allocator,
.arch = arch,
.name = name,
.file = file,
.syslibroot = syslibroot,
};
dylib.parse() catch |err| switch (err) {
error.EndOfStream, error.NotDylib => {
try file.seekTo(0);
var lib_stub = LibStub.loadFromFile(allocator, file) catch {
dylib.deinit();
allocator.destroy(dylib);
return null;
};
defer lib_stub.deinit();
try dylib.parseFromStub(lib_stub);
},
else => |e| return e,
};
var dylibs = std.ArrayList(*Dylib).init(allocator);
defer dylibs.deinit();
try dylibs.append(dylib);
try dylib.parseDependentLibs(&dylibs);
return dylibs.toOwnedSlice();
}
pub fn deinit(self: *Dylib) void {
@ -59,6 +185,11 @@ pub fn deinit(self: *Dylib) void {
}
self.symbols.deinit(self.allocator);
for (self.dependent_libs.keys()) |key| {
self.allocator.free(key);
}
self.dependent_libs.deinit(self.allocator);
if (self.name) |name| {
self.allocator.free(name);
}
@ -81,8 +212,8 @@ pub fn parse(self: *Dylib) !void {
self.header = try reader.readStruct(macho.mach_header_64);
if (self.header.?.filetype != macho.MH_DYLIB) {
log.err("invalid filetype: expected 0x{x}, found 0x{x}", .{ macho.MH_DYLIB, self.header.?.filetype });
return error.MalformedDylib;
log.debug("invalid filetype: expected 0x{x}, found 0x{x}", .{ macho.MH_DYLIB, self.header.?.filetype });
return error.NotDylib;
}
const this_arch: std.Target.Cpu.Arch = switch (self.header.?.cputype) {
@ -103,7 +234,7 @@ pub fn parse(self: *Dylib) !void {
try self.parseSymbols();
}
pub fn readLoadCommands(self: *Dylib, reader: anytype) !void {
fn readLoadCommands(self: *Dylib, reader: anytype) !void {
try self.load_commands.ensureCapacity(self.allocator, self.header.?.ncmds);
var i: u16 = 0;
@ -127,15 +258,10 @@ pub fn readLoadCommands(self: *Dylib, reader: anytype) !void {
}
}
pub fn parseId(self: *Dylib) !void {
fn parseId(self: *Dylib) !void {
const index = self.id_cmd_index orelse {
log.debug("no LC_ID_DYLIB load command found; using hard-coded defaults...", .{});
self.id = .{
.name = try self.allocator.dupe(u8, self.name.?),
.timestamp = 2,
.current_version = 0,
.compatibility_version = 0,
};
self.id = Id.default(try self.allocator.dupe(u8, self.name.?));
return;
};
const id_cmd = self.load_commands.items[index].Dylib;
@ -153,7 +279,7 @@ pub fn parseId(self: *Dylib) !void {
};
}
pub fn parseSymbols(self: *Dylib) !void {
fn parseSymbols(self: *Dylib) !void {
const index = self.symtab_cmd_index orelse return;
const symtab_cmd = self.load_commands.items[index].Symtab;
@ -176,13 +302,171 @@ pub fn parseSymbols(self: *Dylib) !void {
}
}
pub fn isDylib(file: fs.File) !bool {
const header = file.reader().readStruct(macho.mach_header_64) catch |err| switch (err) {
error.EndOfStream => return false,
else => |e| return e,
fn hasTarget(targets: []const []const u8, target: []const u8) bool {
for (targets) |t| {
if (mem.eql(u8, t, target)) return true;
}
return false;
}
fn addObjCClassSymbols(self: *Dylib, sym_name: []const u8) !void {
const expanded = &[_][]const u8{
try std.fmt.allocPrint(self.allocator, "_OBJC_CLASS_$_{s}", .{sym_name}),
try std.fmt.allocPrint(self.allocator, "_OBJC_METACLASS_$_{s}", .{sym_name}),
};
try file.seekTo(0);
return header.filetype == macho.MH_DYLIB;
for (expanded) |sym| {
if (self.symbols.contains(sym)) continue;
try self.symbols.putNoClobber(self.allocator, sym, .{});
}
}
pub fn parseFromStub(self: *Dylib, lib_stub: LibStub) !void {
if (lib_stub.inner.len == 0) return error.EmptyStubFile;
log.debug("parsing shared library from stub '{s}'", .{self.name.?});
const umbrella_lib = lib_stub.inner[0];
var id = Id.default(try self.allocator.dupe(u8, umbrella_lib.install_name));
if (umbrella_lib.current_version) |version| {
try id.parseCurrentVersion(version);
}
if (umbrella_lib.compatibility_version) |version| {
try id.parseCompatibilityVersion(version);
}
self.id = id;
const target_string: []const u8 = switch (self.arch.?) {
.aarch64 => "arm64-macos",
.x86_64 => "x86_64-macos",
else => unreachable,
};
var umbrella_libs = std.StringHashMap(void).init(self.allocator);
defer umbrella_libs.deinit();
for (lib_stub.inner) |stub, stub_index| {
if (!hasTarget(stub.targets, target_string)) continue;
if (stub_index > 0) {
// TODO I thought that we could switch on presence of `parent-umbrella` map;
// however, turns out `libsystem_notify.dylib` is fully reexported by `libSystem.dylib`
// BUT does not feature a `parent-umbrella` map as the only sublib. Apple's bug perhaps?
try umbrella_libs.put(stub.install_name, .{});
}
if (stub.exports) |exports| {
for (exports) |exp| {
if (!hasTarget(exp.targets, target_string)) continue;
if (exp.symbols) |symbols| {
for (symbols) |sym_name| {
if (self.symbols.contains(sym_name)) continue;
try self.symbols.putNoClobber(self.allocator, try self.allocator.dupe(u8, sym_name), {});
}
}
if (exp.objc_classes) |classes| {
for (classes) |sym_name| {
try self.addObjCClassSymbols(sym_name);
}
}
}
}
if (stub.reexports) |reexports| {
for (reexports) |reexp| {
if (!hasTarget(reexp.targets, target_string)) continue;
if (reexp.symbols) |symbols| {
for (symbols) |sym_name| {
if (self.symbols.contains(sym_name)) continue;
try self.symbols.putNoClobber(self.allocator, try self.allocator.dupe(u8, sym_name), {});
}
}
if (reexp.objc_classes) |classes| {
for (classes) |sym_name| {
try self.addObjCClassSymbols(sym_name);
}
}
}
}
if (stub.objc_classes) |classes| {
for (classes) |sym_name| {
try self.addObjCClassSymbols(sym_name);
}
}
}
log.debug("{s}", .{umbrella_lib.install_name});
// TODO track which libs were already parsed in different steps
for (lib_stub.inner) |stub| {
if (!hasTarget(stub.targets, target_string)) continue;
if (stub.reexported_libraries) |reexports| {
for (reexports) |reexp| {
if (!hasTarget(reexp.targets, target_string)) continue;
for (reexp.libraries) |lib| {
if (umbrella_libs.contains(lib)) {
log.debug(" | {s} <= {s}", .{ lib, umbrella_lib.install_name });
continue;
}
log.debug(" | {s}", .{lib});
try self.dependent_libs.put(self.allocator, try self.allocator.dupe(u8, lib), {});
}
}
}
}
}
pub fn parseDependentLibs(self: *Dylib, out: *std.ArrayList(*Dylib)) !void {
outer: for (self.dependent_libs.keys()) |lib| {
const dirname = fs.path.dirname(lib) orelse {
log.warn("unable to resolve dependency {s}", .{lib});
continue;
};
const filename = fs.path.basename(lib);
const without_ext = if (mem.lastIndexOfScalar(u8, filename, '.')) |index|
filename[0..index]
else
filename;
for (&[_][]const u8{ "dylib", "tbd" }) |ext| {
const with_ext = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{
without_ext,
ext,
});
defer self.allocator.free(with_ext);
const lib_path = if (self.syslibroot) |syslibroot|
try fs.path.join(self.allocator, &.{ syslibroot, dirname, with_ext })
else
try fs.path.join(self.allocator, &.{ dirname, with_ext });
log.debug("trying dependency at fully resolved path {s}", .{lib_path});
const dylibs = (try createAndParseFromPath(
self.allocator,
self.arch.?,
lib_path,
self.syslibroot,
)) orelse {
continue;
};
try out.appendSlice(dylibs);
continue :outer;
} else {
log.warn("unable to resolve dependency {s}", .{lib});
}
}
}
pub fn createProxy(self: *Dylib, sym_name: []const u8) !?*Symbol {
@ -197,7 +481,7 @@ pub fn createProxy(self: *Dylib, sym_name: []const u8) !?*Symbol {
.@"type" = .proxy,
.name = name,
},
.file = .{ .dylib = self },
.file = self,
};
return &proxy.base;

View File

@ -11,6 +11,7 @@ const mem = std.mem;
const reloc = @import("reloc.zig");
const Allocator = mem.Allocator;
const Arch = std.Target.Cpu.Arch;
const Relocation = reloc.Relocation;
const Symbol = @import("Symbol.zig");
const parseName = @import("Zld.zig").parseName;
@ -18,7 +19,7 @@ const parseName = @import("Zld.zig").parseName;
usingnamespace @import("commands.zig");
allocator: *Allocator,
arch: ?std.Target.Cpu.Arch = null,
arch: ?Arch = null,
header: ?macho.mach_header_64 = null,
file: ?fs.File = null,
file_offset: ?u32 = null,
@ -173,10 +174,36 @@ const DebugInfo = struct {
}
};
pub fn init(allocator: *Allocator) Object {
return .{
.allocator = allocator,
pub fn createAndParseFromPath(allocator: *Allocator, arch: Arch, path: []const u8) !?*Object {
const file = fs.cwd().openFile(path, .{}) catch |err| switch (err) {
error.FileNotFound => return null,
else => |e| return e,
};
errdefer file.close();
const object = try allocator.create(Object);
errdefer allocator.destroy(object);
const name = try allocator.dupe(u8, path);
errdefer allocator.free(name);
object.* = .{
.allocator = allocator,
.arch = arch,
.name = name,
.file = file,
};
object.parse() catch |err| switch (err) {
error.EndOfStream, error.NotObject => {
object.deinit();
allocator.destroy(object);
return null;
},
else => |e| return e,
};
return object;
}
pub fn deinit(self: *Object) void {
@ -223,11 +250,15 @@ pub fn parse(self: *Object) !void {
self.header = try reader.readStruct(macho.mach_header_64);
if (self.header.?.filetype != macho.MH_OBJECT) {
log.err("invalid filetype: expected 0x{x}, found 0x{x}", .{ macho.MH_OBJECT, self.header.?.filetype });
return error.MalformedObject;
log.debug("invalid filetype: expected 0x{x}, found 0x{x}", .{
macho.MH_OBJECT,
self.header.?.filetype,
});
return error.NotObject;
}
const this_arch: std.Target.Cpu.Arch = switch (self.header.?.cputype) {
const this_arch: Arch = switch (self.header.?.cputype) {
macho.CPU_TYPE_ARM64 => .aarch64,
macho.CPU_TYPE_X86_64 => .x86_64,
else => |value| {
@ -533,12 +564,3 @@ pub fn parseDataInCode(self: *Object) !void {
try self.data_in_code_entries.append(self.allocator, dice);
}
}
pub fn isObject(file: fs.File) !bool {
const header = file.reader().readStruct(macho.mach_header_64) catch |err| switch (err) {
error.EndOfStream => return false,
else => |e| return e,
};
try file.seekTo(0);
return header.filetype == macho.MH_OBJECT;
}

View File

@ -1,130 +0,0 @@
const Stub = @This();
const std = @import("std");
const assert = std.debug.assert;
const fs = std.fs;
const log = std.log.scoped(.stub);
const macho = std.macho;
const mem = std.mem;
const Allocator = mem.Allocator;
const Symbol = @import("Symbol.zig");
pub const LibStub = @import("../tapi.zig").LibStub;
allocator: *Allocator,
arch: ?std.Target.Cpu.Arch = null,
lib_stub: ?LibStub = null,
name: ?[]const u8 = null,
ordinal: ?u16 = null,
id: ?Id = null,
/// Parsed symbol table represented as hash map of symbols'
/// names. We can and should defer creating *Symbols until
/// a symbol is referenced by an object file.
symbols: std.StringArrayHashMapUnmanaged(void) = .{},
pub const Id = struct {
name: []const u8,
timestamp: u32,
current_version: u32,
compatibility_version: u32,
pub fn deinit(id: *Id, allocator: *Allocator) void {
allocator.free(id.name);
}
};
pub fn init(allocator: *Allocator) Stub {
return .{ .allocator = allocator };
}
pub fn deinit(self: *Stub) void {
self.symbols.deinit(self.allocator);
if (self.lib_stub) |*lib_stub| {
lib_stub.deinit();
}
if (self.name) |name| {
self.allocator.free(name);
}
if (self.id) |*id| {
id.deinit(self.allocator);
}
}
pub fn parse(self: *Stub) !void {
const lib_stub = self.lib_stub orelse return error.EmptyStubFile;
if (lib_stub.inner.len == 0) return error.EmptyStubFile;
log.debug("parsing shared library from stub '{s}'", .{self.name.?});
const umbrella_lib = lib_stub.inner[0];
self.id = .{
.name = try self.allocator.dupe(u8, umbrella_lib.install_name),
// TODO parse from the stub
.timestamp = 2,
.current_version = 0,
.compatibility_version = 0,
};
const target_string: []const u8 = switch (self.arch.?) {
.aarch64 => "arm64-macos",
.x86_64 => "x86_64-macos",
else => unreachable,
};
for (lib_stub.inner) |stub| {
if (!hasTarget(stub.targets, target_string)) continue;
if (stub.exports) |exports| {
for (exports) |exp| {
if (!hasTarget(exp.targets, target_string)) continue;
for (exp.symbols) |sym_name| {
if (self.symbols.contains(sym_name)) continue;
try self.symbols.putNoClobber(self.allocator, sym_name, {});
}
}
}
if (stub.reexports) |reexports| {
for (reexports) |reexp| {
if (!hasTarget(reexp.targets, target_string)) continue;
for (reexp.symbols) |sym_name| {
if (self.symbols.contains(sym_name)) continue;
try self.symbols.putNoClobber(self.allocator, sym_name, {});
}
}
}
}
}
fn hasTarget(targets: []const []const u8, target: []const u8) bool {
for (targets) |t| {
if (mem.eql(u8, t, target)) return true;
}
return false;
}
pub fn createProxy(self: *Stub, sym_name: []const u8) !?*Symbol {
if (!self.symbols.contains(sym_name)) return null;
const name = try self.allocator.dupe(u8, sym_name);
const proxy = try self.allocator.create(Symbol.Proxy);
errdefer self.allocator.destroy(proxy);
proxy.* = .{
.base = .{
.@"type" = .proxy,
.name = name,
},
.file = .{ .stub = self },
};
return &proxy.base;
}

View File

@ -7,7 +7,6 @@ const mem = std.mem;
const Allocator = mem.Allocator;
const Dylib = @import("Dylib.zig");
const Object = @import("Object.zig");
const Stub = @import("Stub.zig");
pub const Type = enum {
regular,
@ -85,21 +84,26 @@ pub const Regular = struct {
pub const Proxy = struct {
base: Symbol,
/// Dylib or stub where to locate this symbol.
/// Dynamic binding info - spots within the final
/// executable where this proxy is referenced from.
bind_info: std.ArrayListUnmanaged(struct {
segment_id: u16,
address: u64,
}) = .{},
/// Dylib where to locate this symbol.
/// null means self-reference.
file: ?union(enum) {
dylib: *Dylib,
stub: *Stub,
} = null,
file: ?*Dylib = null,
pub const base_type: Symbol.Type = .proxy;
pub fn deinit(proxy: *Proxy, allocator: *Allocator) void {
proxy.bind_info.deinit(allocator);
}
pub fn dylibOrdinal(proxy: *Proxy) u16 {
const file = proxy.file orelse return 0;
return switch (file) {
.dylib => |dylib| dylib.ordinal.?,
.stub => |stub| stub.ordinal.?,
};
const dylib = proxy.file orelse return 0;
return dylib.ordinal.?;
}
};
@ -129,6 +133,10 @@ pub const Tentative = struct {
pub fn deinit(base: *Symbol, allocator: *Allocator) void {
allocator.free(base.name);
switch (base.@"type") {
.proxy => @fieldParentPtr(Proxy, "base", base).deinit(allocator),
else => {},
}
}
pub fn cast(base: *Symbol, comptime T: type) ?*T {

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,6 @@ const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const MachO = @import("../MachO.zig");
const makeStaticString = MachO.makeStaticString;
const padToIdeal = MachO.padToIdeal;
pub const LoadCommand = union(enum) {
@ -187,11 +186,70 @@ pub const SegmentCommand = struct {
inner: macho.segment_command_64,
sections: std.ArrayListUnmanaged(macho.section_64) = .{},
pub fn empty(inner: macho.segment_command_64) SegmentCommand {
return .{ .inner = inner };
const SegmentOptions = struct {
cmdsize: u32 = @sizeOf(macho.segment_command_64),
vmaddr: u64 = 0,
vmsize: u64 = 0,
fileoff: u64 = 0,
filesize: u64 = 0,
maxprot: macho.vm_prot_t = macho.VM_PROT_NONE,
initprot: macho.vm_prot_t = macho.VM_PROT_NONE,
nsects: u32 = 0,
flags: u32 = 0,
};
pub fn empty(comptime segname: []const u8, opts: SegmentOptions) SegmentCommand {
return .{
.inner = .{
.cmd = macho.LC_SEGMENT_64,
.cmdsize = opts.cmdsize,
.segname = makeStaticString(segname),
.vmaddr = opts.vmaddr,
.vmsize = opts.vmsize,
.fileoff = opts.fileoff,
.filesize = opts.filesize,
.maxprot = opts.maxprot,
.initprot = opts.initprot,
.nsects = opts.nsects,
.flags = opts.flags,
},
};
}
pub fn addSection(self: *SegmentCommand, alloc: *Allocator, section: macho.section_64) !void {
const SectionOptions = struct {
addr: u64 = 0,
size: u64 = 0,
offset: u32 = 0,
@"align": u32 = 0,
reloff: u32 = 0,
nreloc: u32 = 0,
flags: u32 = macho.S_REGULAR,
reserved1: u32 = 0,
reserved2: u32 = 0,
reserved3: u32 = 0,
};
pub fn addSection(
self: *SegmentCommand,
alloc: *Allocator,
comptime sectname: []const u8,
opts: SectionOptions,
) !void {
var section = macho.section_64{
.sectname = makeStaticString(sectname),
.segname = undefined,
.addr = opts.addr,
.size = opts.size,
.offset = opts.offset,
.@"align" = opts.@"align",
.reloff = opts.reloff,
.nreloc = opts.nreloc,
.flags = opts.flags,
.reserved1 = opts.reserved1,
.reserved2 = opts.reserved2,
.reserved3 = opts.reserved3,
};
mem.copy(u8, &section.segname, &self.inner.segname);
try self.sections.append(alloc, section);
self.inner.cmdsize += @sizeOf(macho.section_64);
self.inner.nsects += 1;
@ -338,6 +396,13 @@ pub fn createLoadDylibCommand(
return dylib_cmd;
}
fn makeStaticString(bytes: []const u8) [16]u8 {
var buf = [_]u8{0} ** 16;
assert(bytes.len <= buf.len);
mem.copy(u8, &buf, bytes);
return buf;
}
fn testRead(allocator: *Allocator, buffer: []const u8, expected: anytype) !void {
var stream = io.fixedBufferStream(buffer);
var given = try LoadCommand.read(allocator, stream.reader());

View File

@ -26,6 +26,11 @@ pub const LibStub = struct {
float: f64,
int: u64,
},
compatibility_version: ?union(enum) {
string: []const u8,
float: f64,
int: u64,
},
reexported_libraries: ?[]const struct {
targets: []const []const u8,
libraries: []const []const u8,
@ -36,11 +41,13 @@ pub const LibStub = struct {
},
exports: ?[]const struct {
targets: []const []const u8,
symbols: []const []const u8,
symbols: ?[]const []const u8,
objc_classes: ?[]const []const u8,
},
reexports: ?[]const struct {
targets: []const []const u8,
symbols: []const []const u8,
symbols: ?[]const []const u8,
objc_classes: ?[]const []const u8,
},
allowable_clients: ?[]const struct {
targets: []const []const u8,

View File

@ -42,7 +42,7 @@ pub const Node = struct {
.doc => @fieldParentPtr(Node.Doc, "base", self).deinit(allocator),
.map => @fieldParentPtr(Node.Map, "base", self).deinit(allocator),
.list => @fieldParentPtr(Node.List, "base", self).deinit(allocator),
.value => @fieldParentPtr(Node.Value, "base", self).deinit(allocator),
.value => {},
}
}
@ -180,11 +180,6 @@ pub const Node = struct {
pub const base_tag: Node.Tag = .value;
pub fn deinit(self: *Value, allocator: *Allocator) void {
_ = self;
_ = allocator;
}
pub fn format(
self: *const Value,
comptime fmt: []const u8,

View File

@ -290,6 +290,7 @@ const usage_build_generic =
\\ .c C source code (requires LLVM extensions)
\\ .cpp C++ source code (requires LLVM extensions)
\\ Other C++ extensions: .C .cc .cxx
\\ .m Objective-C source code (requires LLVM extensions)
\\
\\General Options:
\\ -h, --help Print this help and exit
@ -1072,7 +1073,7 @@ fn buildOutputType(
.object, .static_library, .shared_library => {
try link_objects.append(arg);
},
.assembly, .c, .cpp, .h, .ll, .bc => {
.assembly, .c, .cpp, .h, .ll, .bc, .m => {
try c_source_files.append(.{
.src_path = arg,
.extra_flags = try arena.dupe([]const u8, extra_cflags.items),
@ -1135,7 +1136,7 @@ fn buildOutputType(
.positional => {
const file_ext = Compilation.classifyFileExt(mem.spanZ(it.only_arg));
switch (file_ext) {
.assembly, .c, .cpp, .ll, .bc, .h => try c_source_files.append(.{ .src_path = it.only_arg }),
.assembly, .c, .cpp, .ll, .bc, .h, .m => try c_source_files.append(.{ .src_path = it.only_arg }),
.unknown, .shared_library, .object, .static_library => {
try link_objects.append(it.only_arg);
},