mirror of
https://github.com/ziglang/zig.git
synced 2024-12-17 08:40:19 +00:00
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:
commit
350ead9cb2
@ -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"
|
||||
|
@ -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"));
|
||||
|
@ -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 });
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
@ -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
@ -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, §ion.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());
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user