macho: copy over new implementation sources from zld

This commit is contained in:
Jakub Konka 2024-01-09 14:59:34 +01:00
parent 92211135f1
commit 2f94dc939e
26 changed files with 8208 additions and 8406 deletions

View File

@ -603,20 +603,24 @@ set(ZIG_STAGE2_SOURCES
"${CMAKE_SOURCE_DIR}/src/link/MachO/DebugSymbols.zig" "${CMAKE_SOURCE_DIR}/src/link/MachO/DebugSymbols.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/DwarfInfo.zig" "${CMAKE_SOURCE_DIR}/src/link/MachO/DwarfInfo.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/Dylib.zig" "${CMAKE_SOURCE_DIR}/src/link/MachO/Dylib.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/InternalObject.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/Object.zig" "${CMAKE_SOURCE_DIR}/src/link/MachO/Object.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/Relocation.zig" "${CMAKE_SOURCE_DIR}/src/link/MachO/Relocation.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/Trie.zig" "${CMAKE_SOURCE_DIR}/src/link/MachO/Symbol.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/UnwindInfo.zig" "${CMAKE_SOURCE_DIR}/src/link/MachO/UnwindInfo.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/dead_strip.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/dyld_info/bind.zig" "${CMAKE_SOURCE_DIR}/src/link/MachO/dyld_info/bind.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/dyld_info/Rebase.zig" "${CMAKE_SOURCE_DIR}/src/link/MachO/dyld_info/Rebase.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/dead_strip.zig" "${CMAKE_SOURCE_DIR}/src/link/MachO/dyld_info/Trie.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/eh_frame.zig" "${CMAKE_SOURCE_DIR}/src/link/MachO/eh_frame.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/fat.zig" "${CMAKE_SOURCE_DIR}/src/link/MachO/fat.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/file.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/hasher.zig" "${CMAKE_SOURCE_DIR}/src/link/MachO/hasher.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/load_commands.zig" "${CMAKE_SOURCE_DIR}/src/link/MachO/load_commands.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/relocatable.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/synthetic.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/thunks.zig" "${CMAKE_SOURCE_DIR}/src/link/MachO/thunks.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/uuid.zig" "${CMAKE_SOURCE_DIR}/src/link/MachO/uuid.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/zld.zig"
"${CMAKE_SOURCE_DIR}/src/link/Plan9.zig" "${CMAKE_SOURCE_DIR}/src/link/Plan9.zig"
"${CMAKE_SOURCE_DIR}/src/link/Plan9/aout.zig" "${CMAKE_SOURCE_DIR}/src/link/Plan9/aout.zig"
"${CMAKE_SOURCE_DIR}/src/link/Wasm.zig" "${CMAKE_SOURCE_DIR}/src/link/Wasm.zig"

View File

@ -1,20 +1,15 @@
file: fs.File, path: []const u8,
fat_offset: u64, data: []const u8,
name: []const u8,
header: ar_hdr = undefined,
/// Parsed table of contents. objects: std.ArrayListUnmanaged(Object) = .{},
/// Each symbol name points to a list of all definition
/// sites within the current static archive.
toc: std.StringArrayHashMapUnmanaged(std.ArrayListUnmanaged(u32)) = .{},
// Archive files start with the ARMAG identifying string. Then follows a // Archive files start with the ARMAG identifying string. Then follows a
// `struct ar_hdr', and as many bytes of member file data as its `ar_size' // `struct ar_hdr', and as many bytes of member file data as its `ar_size'
// member indicates, for each member file. // member indicates, for each member file.
/// String that begins an archive file. /// String that begins an archive file.
const ARMAG: *const [SARMAG:0]u8 = "!<arch>\n"; pub const ARMAG: *const [SARMAG:0]u8 = "!<arch>\n";
/// Size of that string. /// Size of that string.
const SARMAG: u4 = 8; pub const SARMAG: u4 = 8;
/// String in ar_fmag at the end of each header. /// String in ar_fmag at the end of each header.
const ARFMAG: *const [2:0]u8 = "`\n"; const ARFMAG: *const [2:0]u8 = "`\n";
@ -41,177 +36,93 @@ const ar_hdr = extern struct {
/// Always contains ARFMAG. /// Always contains ARFMAG.
ar_fmag: [2]u8, ar_fmag: [2]u8,
const NameOrLength = union(enum) {
Name: []const u8,
Length: u32,
};
fn nameOrLength(self: ar_hdr) !NameOrLength {
const value = getValue(&self.ar_name);
const slash_index = mem.indexOf(u8, value, "/") orelse return error.MalformedArchive;
const len = value.len;
if (slash_index == len - 1) {
// Name stored directly
return NameOrLength{ .Name = value };
} else {
// Name follows the header directly and its length is encoded in
// the name field.
const length = try std.fmt.parseInt(u32, value[slash_index + 1 ..], 10);
return NameOrLength{ .Length = length };
}
}
fn date(self: ar_hdr) !u64 { fn date(self: ar_hdr) !u64 {
const value = getValue(&self.ar_date); const value = mem.trimRight(u8, &self.ar_date, &[_]u8{@as(u8, 0x20)});
return std.fmt.parseInt(u64, value, 10); return std.fmt.parseInt(u64, value, 10);
} }
fn size(self: ar_hdr) !u32 { fn size(self: ar_hdr) !u32 {
const value = getValue(&self.ar_size); const value = mem.trimRight(u8, &self.ar_size, &[_]u8{@as(u8, 0x20)});
return std.fmt.parseInt(u32, value, 10); return std.fmt.parseInt(u32, value, 10);
} }
fn getValue(raw: []const u8) []const u8 { fn name(self: *const ar_hdr) ?[]const u8 {
return mem.trimRight(u8, raw, &[_]u8{@as(u8, 0x20)}); const value = &self.ar_name;
if (mem.startsWith(u8, value, "#1/")) return null;
const sentinel = mem.indexOfScalar(u8, value, '/') orelse value.len;
return value[0..sentinel];
}
fn nameLength(self: ar_hdr) !?u32 {
const value = &self.ar_name;
if (!mem.startsWith(u8, value, "#1/")) return null;
const trimmed = mem.trimRight(u8, self.ar_name["#1/".len..], &[_]u8{0x20});
return try std.fmt.parseInt(u32, trimmed, 10);
} }
}; };
pub fn isArchive(file: fs.File, fat_offset: u64) bool {
const reader = file.reader();
const magic = reader.readBytesNoEof(SARMAG) catch return false;
defer file.seekTo(fat_offset) catch {};
return mem.eql(u8, &magic, ARMAG);
}
pub fn deinit(self: *Archive, allocator: Allocator) void { pub fn deinit(self: *Archive, allocator: Allocator) void {
self.file.close(); self.objects.deinit(allocator);
for (self.toc.keys()) |*key| {
allocator.free(key.*);
}
for (self.toc.values()) |*value| {
value.deinit(allocator);
}
self.toc.deinit(allocator);
allocator.free(self.name);
} }
pub fn parse(self: *Archive, allocator: Allocator, reader: anytype) !void { pub fn parse(self: *Archive, arena: Allocator, macho_file: *MachO) !void {
_ = try reader.readBytesNoEof(SARMAG); const gpa = macho_file.base.allocator;
self.header = try reader.readStruct(ar_hdr);
const name_or_length = try self.header.nameOrLength();
const embedded_name = try parseName(allocator, name_or_length, reader);
log.debug("parsing archive '{s}' at '{s}'", .{ embedded_name, self.name });
defer allocator.free(embedded_name);
try self.parseTableOfContents(allocator, reader); var stream = std.io.fixedBufferStream(self.data);
} const reader = stream.reader();
fn parseName(allocator: Allocator, name_or_length: ar_hdr.NameOrLength, reader: anytype) ![]u8 {
var name: []u8 = undefined;
switch (name_or_length) {
.Name => |n| {
name = try allocator.dupe(u8, n);
},
.Length => |len| {
var n = try allocator.alloc(u8, len);
defer allocator.free(n);
try reader.readNoEof(n);
const actual_len = mem.indexOfScalar(u8, n, @as(u8, 0)) orelse n.len;
name = try allocator.dupe(u8, n[0..actual_len]);
},
}
return name;
}
fn parseTableOfContents(self: *Archive, allocator: Allocator, reader: anytype) !void {
const symtab_size = try reader.readInt(u32, .little);
const symtab = try allocator.alloc(u8, symtab_size);
defer allocator.free(symtab);
reader.readNoEof(symtab) catch {
log.debug("incomplete symbol table: expected symbol table of length 0x{x}", .{symtab_size});
return error.MalformedArchive;
};
const strtab_size = try reader.readInt(u32, .little);
const strtab = try allocator.alloc(u8, strtab_size);
defer allocator.free(strtab);
reader.readNoEof(strtab) catch {
log.debug("incomplete symbol table: expected string table of length 0x{x}", .{strtab_size});
return error.MalformedArchive;
};
var symtab_stream = std.io.fixedBufferStream(symtab);
var symtab_reader = symtab_stream.reader();
while (true) { while (true) {
const n_strx = symtab_reader.readInt(u32, .little) catch |err| switch (err) { if (stream.pos >= self.data.len) break;
error.EndOfStream => break, if (!mem.isAligned(stream.pos, 2)) stream.pos += 1;
else => |e| return e,
};
const object_offset = try symtab_reader.readInt(u32, .little);
const sym_name = mem.sliceTo(@as([*:0]const u8, @ptrCast(strtab.ptr + n_strx)), 0); const hdr = try reader.readStruct(ar_hdr);
const owned_name = try allocator.dupe(u8, sym_name);
const res = try self.toc.getOrPut(allocator, owned_name);
defer if (res.found_existing) allocator.free(owned_name);
if (!res.found_existing) { if (!mem.eql(u8, &hdr.ar_fmag, ARFMAG)) {
res.value_ptr.* = .{}; macho_file.base.fatal("{s}: invalid header delimiter: expected '{s}', found '{s}'", .{
self.path, std.fmt.fmtSliceEscapeLower(ARFMAG), std.fmt.fmtSliceEscapeLower(&hdr.ar_fmag),
});
return error.ParseFailed;
} }
try res.value_ptr.append(allocator, object_offset); var size = try hdr.size();
const name = name: {
if (hdr.name()) |n| break :name try arena.dupe(u8, n);
if (try hdr.nameLength()) |len| {
size -= len;
const buf = try arena.alloc(u8, len);
try reader.readNoEof(buf);
const actual_len = mem.indexOfScalar(u8, buf, @as(u8, 0)) orelse len;
break :name buf[0..actual_len];
}
unreachable;
};
defer {
_ = stream.seekBy(size) catch {};
}
if (mem.eql(u8, name, "__.SYMDEF") or mem.eql(u8, name, "__.SYMDEF SORTED")) continue;
const object = Object{
.archive = self.path,
.path = name,
.data = self.data[stream.pos..][0..size],
.index = undefined,
.alive = false,
.mtime = hdr.date() catch 0,
};
log.debug("extracting object '{s}' from archive '{s}'", .{ object.path, self.path });
try self.objects.append(gpa, object);
} }
} }
pub fn parseObject(self: Archive, gpa: Allocator, offset: u32) !Object {
const reader = self.file.reader();
try reader.context.seekTo(self.fat_offset + offset);
const object_header = try reader.readStruct(ar_hdr);
const name_or_length = try object_header.nameOrLength();
const object_name = try parseName(gpa, name_or_length, reader);
defer gpa.free(object_name);
log.debug("extracting object '{s}' from archive '{s}'", .{ object_name, self.name });
const name = name: {
var buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;
const path = try std.os.realpath(self.name, &buffer);
break :name try std.fmt.allocPrint(gpa, "{s}({s})", .{ path, object_name });
};
const object_name_len = switch (name_or_length) {
.Name => 0,
.Length => |len| len,
};
const object_size = (try object_header.size()) - object_name_len;
const contents = try gpa.allocWithOptions(u8, object_size, @alignOf(u64), null);
const amt = try reader.readAll(contents);
if (amt != object_size) {
return error.InputOutput;
}
var object = Object{
.name = name,
.mtime = object_header.date() catch 0,
.contents = contents,
};
try object.parse(gpa);
return object;
}
const Archive = @This();
const std = @import("std");
const assert = std.debug.assert;
const fs = std.fs;
const log = std.log.scoped(.link); const log = std.log.scoped(.link);
const macho = std.macho; const macho = std.macho;
const mem = std.mem; const mem = std.mem;
const std = @import("std");
const Allocator = mem.Allocator; const Allocator = mem.Allocator;
const Archive = @This();
const MachO = @import("../MachO.zig");
const Object = @import("Object.zig"); const Object = @import("Object.zig");

File diff suppressed because it is too large Load Diff

View File

@ -1,175 +1,17 @@
page_size: u16, const CodeSignature = @This();
code_directory: CodeDirectory,
requirements: ?Requirements = null,
entitlements: ?Entitlements = null,
signature: ?Signature = null,
pub fn init(page_size: u16) CodeSignature { const std = @import("std");
return .{ const assert = std.debug.assert;
.page_size = page_size, const fs = std.fs;
.code_directory = CodeDirectory.init(page_size), const log = std.log.scoped(.link);
}; const macho = std.macho;
} const mem = std.mem;
const testing = std.testing;
pub fn deinit(self: *CodeSignature, allocator: Allocator) void { const Allocator = mem.Allocator;
self.code_directory.deinit(allocator); const Hasher = @import("hasher.zig").ParallelHasher;
if (self.requirements) |*req| { const MachO = @import("../MachO.zig");
req.deinit(allocator); const Sha256 = std.crypto.hash.sha2.Sha256;
} const Zld = @import("../Zld.zig");
if (self.entitlements) |*ents| {
ents.deinit(allocator);
}
if (self.signature) |*sig| {
sig.deinit(allocator);
}
}
pub fn addEntitlements(self: *CodeSignature, allocator: Allocator, path: []const u8) !void {
const file = try fs.cwd().openFile(path, .{});
defer file.close();
const inner = try file.readToEndAlloc(allocator, std.math.maxInt(u32));
self.entitlements = .{ .inner = inner };
}
pub const WriteOpts = struct {
file: fs.File,
exec_seg_base: u64,
exec_seg_limit: u64,
file_size: u32,
output_mode: std.builtin.OutputMode,
};
pub fn writeAdhocSignature(
self: *CodeSignature,
comp: *const Compilation,
opts: WriteOpts,
writer: anytype,
) !void {
const gpa = comp.gpa;
var header: macho.SuperBlob = .{
.magic = macho.CSMAGIC_EMBEDDED_SIGNATURE,
.length = @sizeOf(macho.SuperBlob),
.count = 0,
};
var blobs = std.ArrayList(Blob).init(gpa);
defer blobs.deinit();
self.code_directory.inner.execSegBase = opts.exec_seg_base;
self.code_directory.inner.execSegLimit = opts.exec_seg_limit;
self.code_directory.inner.execSegFlags = if (opts.output_mode == .Exe) macho.CS_EXECSEG_MAIN_BINARY else 0;
self.code_directory.inner.codeLimit = opts.file_size;
const total_pages = @as(u32, @intCast(mem.alignForward(usize, opts.file_size, self.page_size) / self.page_size));
try self.code_directory.code_slots.ensureTotalCapacityPrecise(gpa, total_pages);
self.code_directory.code_slots.items.len = total_pages;
self.code_directory.inner.nCodeSlots = total_pages;
// Calculate hash for each page (in file) and write it to the buffer
var hasher = Hasher(Sha256){ .allocator = gpa, .thread_pool = comp.thread_pool };
try hasher.hash(opts.file, self.code_directory.code_slots.items, .{
.chunk_size = self.page_size,
.max_file_size = opts.file_size,
});
try blobs.append(.{ .code_directory = &self.code_directory });
header.length += @sizeOf(macho.BlobIndex);
header.count += 1;
var hash: [hash_size]u8 = undefined;
if (self.requirements) |*req| {
var buf = std.ArrayList(u8).init(gpa);
defer buf.deinit();
try req.write(buf.writer());
Sha256.hash(buf.items, &hash, .{});
self.code_directory.addSpecialHash(req.slotType(), hash);
try blobs.append(.{ .requirements = req });
header.count += 1;
header.length += @sizeOf(macho.BlobIndex) + req.size();
}
if (self.entitlements) |*ents| {
var buf = std.ArrayList(u8).init(gpa);
defer buf.deinit();
try ents.write(buf.writer());
Sha256.hash(buf.items, &hash, .{});
self.code_directory.addSpecialHash(ents.slotType(), hash);
try blobs.append(.{ .entitlements = ents });
header.count += 1;
header.length += @sizeOf(macho.BlobIndex) + ents.size();
}
if (self.signature) |*sig| {
try blobs.append(.{ .signature = sig });
header.count += 1;
header.length += @sizeOf(macho.BlobIndex) + sig.size();
}
self.code_directory.inner.hashOffset =
@sizeOf(macho.CodeDirectory) + @as(u32, @intCast(self.code_directory.ident.len + 1 + self.code_directory.inner.nSpecialSlots * hash_size));
self.code_directory.inner.length = self.code_directory.size();
header.length += self.code_directory.size();
try writer.writeInt(u32, header.magic, .big);
try writer.writeInt(u32, header.length, .big);
try writer.writeInt(u32, header.count, .big);
var offset: u32 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) * @as(u32, @intCast(blobs.items.len));
for (blobs.items) |blob| {
try writer.writeInt(u32, blob.slotType(), .big);
try writer.writeInt(u32, offset, .big);
offset += blob.size();
}
for (blobs.items) |blob| {
try blob.write(writer);
}
}
pub fn size(self: CodeSignature) u32 {
var ssize: u32 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) + self.code_directory.size();
if (self.requirements) |req| {
ssize += @sizeOf(macho.BlobIndex) + req.size();
}
if (self.entitlements) |ent| {
ssize += @sizeOf(macho.BlobIndex) + ent.size();
}
if (self.signature) |sig| {
ssize += @sizeOf(macho.BlobIndex) + sig.size();
}
return ssize;
}
pub fn estimateSize(self: CodeSignature, file_size: u64) u32 {
var ssize: u64 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) + self.code_directory.size();
// Approx code slots
const total_pages = mem.alignForward(u64, file_size, self.page_size) / self.page_size;
ssize += total_pages * hash_size;
var n_special_slots: u32 = 0;
if (self.requirements) |req| {
ssize += @sizeOf(macho.BlobIndex) + req.size();
n_special_slots = @max(n_special_slots, req.slotType());
}
if (self.entitlements) |ent| {
ssize += @sizeOf(macho.BlobIndex) + ent.size() + hash_size;
n_special_slots = @max(n_special_slots, ent.slotType());
}
if (self.signature) |sig| {
ssize += @sizeOf(macho.BlobIndex) + sig.size();
}
ssize += n_special_slots * hash_size;
return @as(u32, @intCast(mem.alignForward(u64, ssize, @sizeOf(u64))));
}
pub fn clear(self: *CodeSignature, allocator: Allocator) void {
self.code_directory.deinit(allocator);
self.code_directory = CodeDirectory.init(self.page_size);
}
const hash_size = Sha256.digest_length; const hash_size = Sha256.digest_length;
@ -257,7 +99,7 @@ const CodeDirectory = struct {
fn addSpecialHash(self: *CodeDirectory, index: u32, hash: [hash_size]u8) void { fn addSpecialHash(self: *CodeDirectory, index: u32, hash: [hash_size]u8) void {
assert(index > 0); assert(index > 0);
self.inner.nSpecialSlots = @max(self.inner.nSpecialSlots, index); self.inner.nSpecialSlots = @max(self.inner.nSpecialSlots, index);
self.special_slots[index - 1] = hash; @memcpy(&self.special_slots[index - 1], &hash);
} }
fn slotType(self: CodeDirectory) u32 { fn slotType(self: CodeDirectory) u32 {
@ -376,17 +218,175 @@ const Signature = struct {
} }
}; };
const CodeSignature = @This(); page_size: u16,
code_directory: CodeDirectory,
requirements: ?Requirements = null,
entitlements: ?Entitlements = null,
signature: ?Signature = null,
const std = @import("std"); pub fn init(page_size: u16) CodeSignature {
const assert = std.debug.assert; return .{
const fs = std.fs; .page_size = page_size,
const log = std.log.scoped(.link); .code_directory = CodeDirectory.init(page_size),
const macho = std.macho; };
const mem = std.mem; }
const testing = std.testing;
const Allocator = mem.Allocator; pub fn deinit(self: *CodeSignature, allocator: Allocator) void {
const Compilation = @import("../../Compilation.zig"); self.code_directory.deinit(allocator);
const Hasher = @import("hasher.zig").ParallelHasher; if (self.requirements) |*req| {
const Sha256 = std.crypto.hash.sha2.Sha256; req.deinit(allocator);
}
if (self.entitlements) |*ents| {
ents.deinit(allocator);
}
if (self.signature) |*sig| {
sig.deinit(allocator);
}
}
pub fn addEntitlements(self: *CodeSignature, allocator: Allocator, path: []const u8) !void {
const file = try fs.cwd().openFile(path, .{});
defer file.close();
const inner = try file.readToEndAlloc(allocator, std.math.maxInt(u32));
self.entitlements = .{ .inner = inner };
}
pub const WriteOpts = struct {
file: fs.File,
exec_seg_base: u64,
exec_seg_limit: u64,
file_size: u32,
dylib: bool,
};
pub fn writeAdhocSignature(
self: *CodeSignature,
macho_file: *MachO,
opts: WriteOpts,
writer: anytype,
) !void {
const allocator = macho_file.base.allocator;
var header: macho.SuperBlob = .{
.magic = macho.CSMAGIC_EMBEDDED_SIGNATURE,
.length = @sizeOf(macho.SuperBlob),
.count = 0,
};
var blobs = std.ArrayList(Blob).init(allocator);
defer blobs.deinit();
self.code_directory.inner.execSegBase = opts.exec_seg_base;
self.code_directory.inner.execSegLimit = opts.exec_seg_limit;
self.code_directory.inner.execSegFlags = if (!opts.dylib) macho.CS_EXECSEG_MAIN_BINARY else 0;
self.code_directory.inner.codeLimit = opts.file_size;
const total_pages = @as(u32, @intCast(mem.alignForward(usize, opts.file_size, self.page_size) / self.page_size));
try self.code_directory.code_slots.ensureTotalCapacityPrecise(allocator, total_pages);
self.code_directory.code_slots.items.len = total_pages;
self.code_directory.inner.nCodeSlots = total_pages;
// Calculate hash for each page (in file) and write it to the buffer
var hasher = Hasher(Sha256){ .allocator = allocator, .thread_pool = macho_file.base.thread_pool };
try hasher.hash(opts.file, self.code_directory.code_slots.items, .{
.chunk_size = self.page_size,
.max_file_size = opts.file_size,
});
try blobs.append(.{ .code_directory = &self.code_directory });
header.length += @sizeOf(macho.BlobIndex);
header.count += 1;
var hash: [hash_size]u8 = undefined;
if (self.requirements) |*req| {
var buf = std.ArrayList(u8).init(allocator);
defer buf.deinit();
try req.write(buf.writer());
Sha256.hash(buf.items, &hash, .{});
self.code_directory.addSpecialHash(req.slotType(), hash);
try blobs.append(.{ .requirements = req });
header.count += 1;
header.length += @sizeOf(macho.BlobIndex) + req.size();
}
if (self.entitlements) |*ents| {
var buf = std.ArrayList(u8).init(allocator);
defer buf.deinit();
try ents.write(buf.writer());
Sha256.hash(buf.items, &hash, .{});
self.code_directory.addSpecialHash(ents.slotType(), hash);
try blobs.append(.{ .entitlements = ents });
header.count += 1;
header.length += @sizeOf(macho.BlobIndex) + ents.size();
}
if (self.signature) |*sig| {
try blobs.append(.{ .signature = sig });
header.count += 1;
header.length += @sizeOf(macho.BlobIndex) + sig.size();
}
self.code_directory.inner.hashOffset =
@sizeOf(macho.CodeDirectory) + @as(u32, @intCast(self.code_directory.ident.len + 1 + self.code_directory.inner.nSpecialSlots * hash_size));
self.code_directory.inner.length = self.code_directory.size();
header.length += self.code_directory.size();
try writer.writeInt(u32, header.magic, .big);
try writer.writeInt(u32, header.length, .big);
try writer.writeInt(u32, header.count, .big);
var offset: u32 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) * @as(u32, @intCast(blobs.items.len));
for (blobs.items) |blob| {
try writer.writeInt(u32, blob.slotType(), .big);
try writer.writeInt(u32, offset, .big);
offset += blob.size();
}
for (blobs.items) |blob| {
try blob.write(writer);
}
}
pub fn size(self: CodeSignature) u32 {
var ssize: u32 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) + self.code_directory.size();
if (self.requirements) |req| {
ssize += @sizeOf(macho.BlobIndex) + req.size();
}
if (self.entitlements) |ent| {
ssize += @sizeOf(macho.BlobIndex) + ent.size();
}
if (self.signature) |sig| {
ssize += @sizeOf(macho.BlobIndex) + sig.size();
}
return ssize;
}
pub fn estimateSize(self: CodeSignature, file_size: u64) u32 {
var ssize: u64 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) + self.code_directory.size();
// Approx code slots
const total_pages = mem.alignForward(u64, file_size, self.page_size) / self.page_size;
ssize += total_pages * hash_size;
var n_special_slots: u32 = 0;
if (self.requirements) |req| {
ssize += @sizeOf(macho.BlobIndex) + req.size();
n_special_slots = @max(n_special_slots, req.slotType());
}
if (self.entitlements) |ent| {
ssize += @sizeOf(macho.BlobIndex) + ent.size() + hash_size;
n_special_slots = @max(n_special_slots, ent.slotType());
}
if (self.signature) |sig| {
ssize += @sizeOf(macho.BlobIndex) + sig.size();
}
ssize += n_special_slots * hash_size;
return @as(u32, @intCast(mem.alignForward(u64, ssize, @sizeOf(u64))));
}
pub fn clear(self: *CodeSignature, allocator: Allocator) void {
self.code_directory.deinit(allocator);
self.code_directory = CodeDirectory.init(self.page_size);
}

View File

@ -2,377 +2,175 @@ debug_info: []const u8,
debug_abbrev: []const u8, debug_abbrev: []const u8,
debug_str: []const u8, debug_str: []const u8,
pub fn getCompileUnitIterator(self: DwarfInfo) CompileUnitIterator { /// Abbreviation table indexed by offset in the .debug_abbrev bytestream
return .{ .ctx = self }; abbrev_tables: std.AutoArrayHashMapUnmanaged(u64, AbbrevTable) = .{},
/// List of compile units as they appear in the .debug_info bytestream
compile_units: std.ArrayListUnmanaged(CompileUnit) = .{},
pub fn init(dw: *DwarfInfo, allocator: Allocator) !void {
try dw.parseAbbrevTables(allocator);
try dw.parseCompileUnits(allocator);
} }
const CompileUnitIterator = struct { pub fn deinit(dw: *DwarfInfo, allocator: Allocator) void {
ctx: DwarfInfo, dw.abbrev_tables.deinit(allocator);
pos: usize = 0, for (dw.compile_units.items) |*cu| {
cu.deinit(allocator);
pub fn next(self: *CompileUnitIterator) !?CompileUnit {
if (self.pos >= self.ctx.debug_info.len) return null;
var stream = std.io.fixedBufferStream(self.ctx.debug_info[self.pos..]);
var creader = std.io.countingReader(stream.reader());
const reader = creader.reader();
const cuh = try CompileUnit.Header.read(reader);
const total_length = cuh.length + @as(u64, if (cuh.is_64bit) @sizeOf(u64) else @sizeOf(u32));
const offset = math.cast(usize, creader.bytes_read) orelse return error.Overflow;
const cu = CompileUnit{
.cuh = cuh,
.debug_info_off = self.pos + offset,
};
self.pos += (math.cast(usize, total_length) orelse return error.Overflow);
return cu;
} }
}; dw.compile_units.deinit(allocator);
pub fn genSubprogramLookupByName(
self: DwarfInfo,
compile_unit: CompileUnit,
abbrev_lookup: AbbrevLookupTable,
lookup: *SubprogramLookupByName,
) !void {
var abbrev_it = compile_unit.getAbbrevEntryIterator(self);
while (try abbrev_it.next(abbrev_lookup)) |entry| switch (entry.tag) {
dwarf.TAG.subprogram => {
var attr_it = entry.getAttributeIterator(self, compile_unit.cuh);
var name: ?[]const u8 = null;
var low_pc: ?u64 = null;
var high_pc: ?u64 = null;
while (try attr_it.next()) |attr| switch (attr.name) {
dwarf.AT.name => if (attr.getString(self, compile_unit.cuh)) |str| {
name = str;
},
dwarf.AT.low_pc => {
if (attr.getAddr(self, compile_unit.cuh)) |addr| {
low_pc = addr;
}
if (try attr.getConstant(self)) |constant| {
low_pc = @as(u64, @intCast(constant));
}
},
dwarf.AT.high_pc => {
if (attr.getAddr(self, compile_unit.cuh)) |addr| {
high_pc = addr;
}
if (try attr.getConstant(self)) |constant| {
high_pc = @as(u64, @intCast(constant));
}
},
else => {},
};
if (name == null or low_pc == null or high_pc == null) continue;
try lookup.putNoClobber(name.?, .{ .addr = low_pc.?, .size = high_pc.? });
},
else => {},
};
} }
pub fn genAbbrevLookupByKind(self: DwarfInfo, off: usize, lookup: *AbbrevLookupTable) !void { fn getString(dw: DwarfInfo, off: u64) [:0]const u8 {
const data = self.debug_abbrev[off..]; assert(off < dw.debug_str.len);
var stream = std.io.fixedBufferStream(data); return mem.sliceTo(@as([*:0]const u8, @ptrCast(dw.debug_str.ptr + off)), 0);
var creader = std.io.countingReader(stream.reader());
const reader = creader.reader();
while (true) {
const kind = try leb.readULEB128(u64, reader);
if (kind == 0) break;
const pos = math.cast(usize, creader.bytes_read) orelse return error.Overflow;
_ = try leb.readULEB128(u64, reader); // TAG
_ = try reader.readByte(); // CHILDREN
while (true) {
const name = try leb.readULEB128(u64, reader);
const form = try leb.readULEB128(u64, reader);
if (name == 0 and form == 0) break;
}
const next_pos = math.cast(usize, creader.bytes_read) orelse return error.Overflow;
try lookup.putNoClobber(kind, .{
.pos = pos,
.len = next_pos - pos - 2,
});
}
} }
pub const CompileUnit = struct { fn parseAbbrevTables(dw: *DwarfInfo, allocator: Allocator) !void {
cuh: Header, const tracy = trace(@src());
debug_info_off: usize, defer tracy.end();
pub const Header = struct { const debug_abbrev = dw.debug_abbrev;
is_64bit: bool,
length: u64,
version: u16,
debug_abbrev_offset: u64,
address_size: u8,
fn read(reader: anytype) !Header {
var length: u64 = try reader.readInt(u32, .little);
const is_64bit = length == 0xffffffff;
if (is_64bit) {
length = try reader.readInt(u64, .little);
}
const version = try reader.readInt(u16, .little);
const debug_abbrev_offset = if (is_64bit)
try reader.readInt(u64, .little)
else
try reader.readInt(u32, .little);
const address_size = try reader.readInt(u8, .little);
return Header{
.is_64bit = is_64bit,
.length = length,
.version = version,
.debug_abbrev_offset = debug_abbrev_offset,
.address_size = address_size,
};
}
};
inline fn getDebugInfo(self: CompileUnit, ctx: DwarfInfo) []const u8 {
return ctx.debug_info[self.debug_info_off..][0..self.cuh.length];
}
pub fn getAbbrevEntryIterator(self: CompileUnit, ctx: DwarfInfo) AbbrevEntryIterator {
return .{ .cu = self, .ctx = ctx };
}
};
const AbbrevEntryIterator = struct {
cu: CompileUnit,
ctx: DwarfInfo,
pos: usize = 0,
pub fn next(self: *AbbrevEntryIterator, lookup: AbbrevLookupTable) !?AbbrevEntry {
if (self.pos + self.cu.debug_info_off >= self.ctx.debug_info.len) return null;
const debug_info = self.ctx.debug_info[self.pos + self.cu.debug_info_off ..];
var stream = std.io.fixedBufferStream(debug_info);
var creader = std.io.countingReader(stream.reader());
const reader = creader.reader();
const kind = try leb.readULEB128(u64, reader);
self.pos += (math.cast(usize, creader.bytes_read) orelse return error.Overflow);
if (kind == 0) {
return AbbrevEntry.null();
}
const abbrev_pos = lookup.get(kind) orelse return null;
const len = try findAbbrevEntrySize(
self.ctx,
abbrev_pos.pos,
abbrev_pos.len,
self.pos + self.cu.debug_info_off,
self.cu.cuh,
);
const entry = try getAbbrevEntry(
self.ctx,
abbrev_pos.pos,
abbrev_pos.len,
self.pos + self.cu.debug_info_off,
len,
);
self.pos += len;
return entry;
}
};
pub const AbbrevEntry = struct {
tag: u64,
children: u8,
debug_abbrev_off: usize,
debug_abbrev_len: usize,
debug_info_off: usize,
debug_info_len: usize,
fn @"null"() AbbrevEntry {
return .{
.tag = 0,
.children = dwarf.CHILDREN.no,
.debug_abbrev_off = 0,
.debug_abbrev_len = 0,
.debug_info_off = 0,
.debug_info_len = 0,
};
}
pub fn hasChildren(self: AbbrevEntry) bool {
return self.children == dwarf.CHILDREN.yes;
}
inline fn getDebugInfo(self: AbbrevEntry, ctx: DwarfInfo) []const u8 {
return ctx.debug_info[self.debug_info_off..][0..self.debug_info_len];
}
inline fn getDebugAbbrev(self: AbbrevEntry, ctx: DwarfInfo) []const u8 {
return ctx.debug_abbrev[self.debug_abbrev_off..][0..self.debug_abbrev_len];
}
pub fn getAttributeIterator(self: AbbrevEntry, ctx: DwarfInfo, cuh: CompileUnit.Header) AttributeIterator {
return .{ .entry = self, .ctx = ctx, .cuh = cuh };
}
};
pub const Attribute = struct {
name: u64,
form: u64,
debug_info_off: usize,
debug_info_len: usize,
inline fn getDebugInfo(self: Attribute, ctx: DwarfInfo) []const u8 {
return ctx.debug_info[self.debug_info_off..][0..self.debug_info_len];
}
pub fn getString(self: Attribute, ctx: DwarfInfo, cuh: CompileUnit.Header) ?[]const u8 {
const debug_info = self.getDebugInfo(ctx);
switch (self.form) {
dwarf.FORM.string => {
return mem.sliceTo(@as([*:0]const u8, @ptrCast(debug_info.ptr)), 0);
},
dwarf.FORM.strp => {
const off = if (cuh.is_64bit)
mem.readInt(u64, debug_info[0..8], .little)
else
mem.readInt(u32, debug_info[0..4], .little);
return ctx.getString(off);
},
else => return null,
}
}
pub fn getConstant(self: Attribute, ctx: DwarfInfo) !?i128 {
const debug_info = self.getDebugInfo(ctx);
var stream = std.io.fixedBufferStream(debug_info);
const reader = stream.reader();
return switch (self.form) {
dwarf.FORM.data1 => debug_info[0],
dwarf.FORM.data2 => mem.readInt(u16, debug_info[0..2], .little),
dwarf.FORM.data4 => mem.readInt(u32, debug_info[0..4], .little),
dwarf.FORM.data8 => mem.readInt(u64, debug_info[0..8], .little),
dwarf.FORM.udata => try leb.readULEB128(u64, reader),
dwarf.FORM.sdata => try leb.readILEB128(i64, reader),
else => null,
};
}
pub fn getAddr(self: Attribute, ctx: DwarfInfo, cuh: CompileUnit.Header) ?u64 {
if (self.form != dwarf.FORM.addr) return null;
const debug_info = self.getDebugInfo(ctx);
return switch (cuh.address_size) {
1 => debug_info[0],
2 => mem.readInt(u16, debug_info[0..2], .little),
4 => mem.readInt(u32, debug_info[0..4], .little),
8 => mem.readInt(u64, debug_info[0..8], .little),
else => unreachable,
};
}
};
const AttributeIterator = struct {
entry: AbbrevEntry,
ctx: DwarfInfo,
cuh: CompileUnit.Header,
debug_abbrev_pos: usize = 0,
debug_info_pos: usize = 0,
pub fn next(self: *AttributeIterator) !?Attribute {
const debug_abbrev = self.entry.getDebugAbbrev(self.ctx);
if (self.debug_abbrev_pos >= debug_abbrev.len) return null;
var stream = std.io.fixedBufferStream(debug_abbrev[self.debug_abbrev_pos..]);
var creader = std.io.countingReader(stream.reader());
const reader = creader.reader();
const name = try leb.readULEB128(u64, reader);
const form = try leb.readULEB128(u64, reader);
self.debug_abbrev_pos += (math.cast(usize, creader.bytes_read) orelse return error.Overflow);
const len = try findFormSize(
self.ctx,
form,
self.debug_info_pos + self.entry.debug_info_off,
self.cuh,
);
const attr = Attribute{
.name = name,
.form = form,
.debug_info_off = self.debug_info_pos + self.entry.debug_info_off,
.debug_info_len = len,
};
self.debug_info_pos += len;
return attr;
}
};
fn getAbbrevEntry(self: DwarfInfo, da_off: usize, da_len: usize, di_off: usize, di_len: usize) !AbbrevEntry {
const debug_abbrev = self.debug_abbrev[da_off..][0..da_len];
var stream = std.io.fixedBufferStream(debug_abbrev); var stream = std.io.fixedBufferStream(debug_abbrev);
var creader = std.io.countingReader(stream.reader()); var creader = std.io.countingReader(stream.reader());
const reader = creader.reader(); const reader = creader.reader();
const tag = try leb.readULEB128(u64, reader); while (true) {
const children = switch (tag) { if (creader.bytes_read >= debug_abbrev.len) break;
std.dwarf.TAG.const_type,
std.dwarf.TAG.packed_type,
std.dwarf.TAG.pointer_type,
std.dwarf.TAG.reference_type,
std.dwarf.TAG.restrict_type,
std.dwarf.TAG.rvalue_reference_type,
std.dwarf.TAG.shared_type,
std.dwarf.TAG.volatile_type,
=> if (creader.bytes_read == da_len) std.dwarf.CHILDREN.no else try reader.readByte(),
else => try reader.readByte(),
};
const pos = math.cast(usize, creader.bytes_read) orelse return error.Overflow; try dw.abbrev_tables.ensureUnusedCapacity(allocator, 1);
const table_gop = dw.abbrev_tables.getOrPutAssumeCapacity(@intCast(creader.bytes_read));
assert(!table_gop.found_existing);
const table = table_gop.value_ptr;
table.* = .{};
return AbbrevEntry{ while (true) {
.tag = tag, const code = try leb.readULEB128(Code, reader);
.children = children, if (code == 0) break;
.debug_abbrev_off = pos + da_off,
.debug_abbrev_len = da_len - pos, try table.decls.ensureUnusedCapacity(allocator, 1);
.debug_info_off = di_off, const decl_gop = table.decls.getOrPutAssumeCapacity(code);
.debug_info_len = di_len, assert(!decl_gop.found_existing);
}; const decl = decl_gop.value_ptr;
decl.* = .{
.code = code,
.tag = undefined,
.children = false,
};
decl.tag = try leb.readULEB128(Tag, reader);
decl.children = (try reader.readByte()) > 0;
while (true) {
const at = try leb.readULEB128(At, reader);
const form = try leb.readULEB128(Form, reader);
if (at == 0 and form == 0) break;
try decl.attrs.ensureUnusedCapacity(allocator, 1);
const attr_gop = decl.attrs.getOrPutAssumeCapacity(at);
assert(!attr_gop.found_existing);
const attr = attr_gop.value_ptr;
attr.* = .{
.at = at,
.form = form,
};
}
}
}
} }
fn findFormSize(self: DwarfInfo, form: u64, di_off: usize, cuh: CompileUnit.Header) !usize { fn parseCompileUnits(dw: *DwarfInfo, allocator: Allocator) !void {
const debug_info = self.debug_info[di_off..]; const tracy = trace(@src());
defer tracy.end();
const debug_info = dw.debug_info;
var stream = std.io.fixedBufferStream(debug_info); var stream = std.io.fixedBufferStream(debug_info);
var creader = std.io.countingReader(stream.reader()); var creader = std.io.countingReader(stream.reader());
const reader = creader.reader(); const reader = creader.reader();
while (true) {
if (creader.bytes_read == debug_info.len) break;
const cu = try dw.compile_units.addOne(allocator);
cu.* = .{
.header = undefined,
.pos = creader.bytes_read,
};
var length: u64 = try reader.readInt(u32, .little);
const is_64bit = length == 0xffffffff;
if (is_64bit) {
length = try reader.readInt(u64, .little);
}
cu.header.format = if (is_64bit) .dwarf64 else .dwarf32;
cu.header.length = length;
cu.header.version = try reader.readInt(u16, .little);
cu.header.debug_abbrev_offset = try readOffset(cu.header.format, reader);
cu.header.address_size = try reader.readInt(u8, .little);
const table = dw.abbrev_tables.get(cu.header.debug_abbrev_offset).?;
try dw.parseDie(allocator, cu, table, null, &creader);
}
}
fn parseDie(
dw: *DwarfInfo,
allocator: Allocator,
cu: *CompileUnit,
table: AbbrevTable,
parent: ?u32,
creader: anytype,
) anyerror!void {
const tracy = trace(@src());
defer tracy.end();
while (creader.bytes_read < cu.nextCompileUnitOffset()) {
const die = try cu.addDie(allocator);
cu.diePtr(die).* = .{ .code = undefined };
if (parent) |p| {
try cu.diePtr(p).children.append(allocator, die);
} else {
try cu.children.append(allocator, die);
}
const code = try leb.readULEB128(Code, creader.reader());
cu.diePtr(die).code = code;
if (code == 0) {
if (parent == null) continue;
return; // Close scope
}
const decl = table.decls.get(code) orelse return error.MalformedDwarf; // TODO better errors
const data = dw.debug_info;
try cu.diePtr(die).values.ensureTotalCapacityPrecise(allocator, decl.attrs.values().len);
for (decl.attrs.values()) |attr| {
const start = creader.bytes_read;
try advanceByFormSize(cu, attr.form, creader);
const end = creader.bytes_read;
cu.diePtr(die).values.appendAssumeCapacity(data[start..end]);
}
if (decl.children) {
// Open scope
try dw.parseDie(allocator, cu, table, die, creader);
}
}
}
fn advanceByFormSize(cu: *CompileUnit, form: Form, creader: anytype) !void {
const tracy = trace(@src());
defer tracy.end();
const reader = creader.reader();
switch (form) { switch (form) {
dwarf.FORM.strp, dwarf.FORM.strp,
dwarf.FORM.sec_offset, dwarf.FORM.sec_offset,
dwarf.FORM.ref_addr, dwarf.FORM.ref_addr,
=> return if (cuh.is_64bit) @sizeOf(u64) else @sizeOf(u32), => {
_ = try readOffset(cu.header.format, reader);
},
dwarf.FORM.addr => return cuh.address_size, dwarf.FORM.addr => try reader.skipBytes(cu.header.address_size, .{}),
dwarf.FORM.block1, dwarf.FORM.block1,
dwarf.FORM.block2, dwarf.FORM.block2,
@ -386,119 +184,285 @@ fn findFormSize(self: DwarfInfo, form: u64, di_off: usize, cuh: CompileUnit.Head
dwarf.FORM.block => try leb.readULEB128(u64, reader), dwarf.FORM.block => try leb.readULEB128(u64, reader),
else => unreachable, else => unreachable,
}; };
var i: u64 = 0; for (0..len) |_| {
while (i < len) : (i += 1) {
_ = try reader.readByte(); _ = try reader.readByte();
} }
return math.cast(usize, creader.bytes_read) orelse error.Overflow;
}, },
dwarf.FORM.exprloc => { dwarf.FORM.exprloc => {
const expr_len = try leb.readULEB128(u64, reader); const len = try leb.readULEB128(u64, reader);
var i: u64 = 0; for (0..len) |_| {
while (i < expr_len) : (i += 1) {
_ = try reader.readByte(); _ = try reader.readByte();
} }
return math.cast(usize, creader.bytes_read) orelse error.Overflow;
}, },
dwarf.FORM.flag_present => return 0, dwarf.FORM.flag_present => {},
dwarf.FORM.data1, dwarf.FORM.data1,
dwarf.FORM.ref1, dwarf.FORM.ref1,
dwarf.FORM.flag, dwarf.FORM.flag,
=> return @sizeOf(u8), => try reader.skipBytes(1, .{}),
dwarf.FORM.data2, dwarf.FORM.data2,
dwarf.FORM.ref2, dwarf.FORM.ref2,
=> return @sizeOf(u16), => try reader.skipBytes(2, .{}),
dwarf.FORM.data4, dwarf.FORM.data4,
dwarf.FORM.ref4, dwarf.FORM.ref4,
=> return @sizeOf(u32), => try reader.skipBytes(4, .{}),
dwarf.FORM.data8, dwarf.FORM.data8,
dwarf.FORM.ref8, dwarf.FORM.ref8,
dwarf.FORM.ref_sig8, dwarf.FORM.ref_sig8,
=> return @sizeOf(u64), => try reader.skipBytes(8, .{}),
dwarf.FORM.udata, dwarf.FORM.udata,
dwarf.FORM.ref_udata, dwarf.FORM.ref_udata,
=> { => {
_ = try leb.readULEB128(u64, reader); _ = try leb.readULEB128(u64, reader);
return math.cast(usize, creader.bytes_read) orelse error.Overflow;
}, },
dwarf.FORM.sdata => { dwarf.FORM.sdata => {
_ = try leb.readILEB128(i64, reader); _ = try leb.readILEB128(i64, reader);
return math.cast(usize, creader.bytes_read) orelse error.Overflow;
}, },
dwarf.FORM.string => { dwarf.FORM.string => {
var count: usize = 0;
while (true) { while (true) {
const byte = try reader.readByte(); const byte = try reader.readByte();
count += 1;
if (byte == 0x0) break; if (byte == 0x0) break;
} }
return count;
}, },
else => { else => {
// TODO figure out how to handle this // TODO better errors
log.debug("unhandled DW_FORM_* value with identifier {x}", .{form}); log.err("unhandled DW_FORM_* value with identifier {x}", .{form});
return error.UnhandledDwFormValue; return error.UnhandledDwFormValue;
}, },
} }
} }
fn findAbbrevEntrySize(self: DwarfInfo, da_off: usize, da_len: usize, di_off: usize, cuh: CompileUnit.Header) !usize { fn readOffset(format: Format, reader: anytype) !u64 {
const debug_abbrev = self.debug_abbrev[da_off..][0..da_len]; return switch (format) {
var stream = std.io.fixedBufferStream(debug_abbrev); .dwarf32 => try reader.readInt(u32, .little),
var creader = std.io.countingReader(stream.reader()); .dwarf64 => try reader.readInt(u64, .little),
const reader = creader.reader(); };
const tag = try leb.readULEB128(u64, reader);
switch (tag) {
std.dwarf.TAG.const_type,
std.dwarf.TAG.packed_type,
std.dwarf.TAG.pointer_type,
std.dwarf.TAG.reference_type,
std.dwarf.TAG.restrict_type,
std.dwarf.TAG.rvalue_reference_type,
std.dwarf.TAG.shared_type,
std.dwarf.TAG.volatile_type,
=> if (creader.bytes_read != da_len) {
_ = try reader.readByte();
},
else => _ = try reader.readByte(),
}
var len: usize = 0;
while (creader.bytes_read < debug_abbrev.len) {
_ = try leb.readULEB128(u64, reader);
const form = try leb.readULEB128(u64, reader);
const form_len = try self.findFormSize(form, di_off + len, cuh);
len += form_len;
}
return len;
} }
fn getString(self: DwarfInfo, off: u64) []const u8 { pub const AbbrevTable = struct {
assert(off < self.debug_str.len); /// Table of abbreviation declarations indexed by their assigned code value
return mem.sliceTo(@as([*:0]const u8, @ptrCast(self.debug_str.ptr + @as(usize, @intCast(off)))), 0); decls: std.AutoArrayHashMapUnmanaged(Code, Decl) = .{},
}
const DwarfInfo = @This(); pub fn deinit(table: *AbbrevTable, gpa: Allocator) void {
for (table.decls.values()) |*decl| {
decl.deinit(gpa);
}
table.decls.deinit(gpa);
}
};
pub const Decl = struct {
code: Code,
tag: Tag,
children: bool,
/// Table of attributes indexed by their AT value
attrs: std.AutoArrayHashMapUnmanaged(At, Attr) = .{},
pub fn deinit(decl: *Decl, gpa: Allocator) void {
decl.attrs.deinit(gpa);
}
};
pub const Attr = struct {
at: At,
form: Form,
};
pub const At = u64;
pub const Code = u64;
pub const Form = u64;
pub const Tag = u64;
pub const CompileUnitHeader = struct {
format: Format,
length: u64,
version: u16,
debug_abbrev_offset: u64,
address_size: u8,
};
pub const CompileUnit = struct {
header: CompileUnitHeader,
pos: usize,
dies: std.ArrayListUnmanaged(Die) = .{},
children: std.ArrayListUnmanaged(Die.Index) = .{},
pub fn deinit(cu: *CompileUnit, gpa: Allocator) void {
for (cu.dies.items) |*die| {
die.deinit(gpa);
}
cu.dies.deinit(gpa);
cu.children.deinit(gpa);
}
pub fn addDie(cu: *CompileUnit, gpa: Allocator) !Die.Index {
const index = @as(Die.Index, @intCast(cu.dies.items.len));
_ = try cu.dies.addOne(gpa);
return index;
}
pub fn diePtr(cu: *CompileUnit, index: Die.Index) *Die {
return &cu.dies.items[index];
}
pub fn getCompileDir(cu: CompileUnit, ctx: DwarfInfo) ?[:0]const u8 {
assert(cu.dies.items.len > 0);
const die = cu.dies.items[0];
const res = die.find(dwarf.AT.comp_dir, cu, ctx) orelse return null;
return res.getString(cu.header.format, ctx);
}
pub fn getSourceFile(cu: CompileUnit, ctx: DwarfInfo) ?[:0]const u8 {
assert(cu.dies.items.len > 0);
const die = cu.dies.items[0];
const res = die.find(dwarf.AT.name, cu, ctx) orelse return null;
return res.getString(cu.header.format, ctx);
}
pub fn nextCompileUnitOffset(cu: CompileUnit) u64 {
return cu.pos + switch (cu.header.format) {
.dwarf32 => @as(u64, 4),
.dwarf64 => 12,
} + cu.header.length;
}
};
pub const Die = struct {
code: Code,
values: std.ArrayListUnmanaged([]const u8) = .{},
children: std.ArrayListUnmanaged(Die.Index) = .{},
pub fn deinit(die: *Die, gpa: Allocator) void {
die.values.deinit(gpa);
die.children.deinit(gpa);
}
pub fn find(die: Die, at: At, cu: CompileUnit, ctx: DwarfInfo) ?DieValue {
const table = ctx.abbrev_tables.get(cu.header.debug_abbrev_offset) orelse return null;
const decl = table.decls.get(die.code).?;
const index = decl.attrs.getIndex(at) orelse return null;
const attr = decl.attrs.values()[index];
const value = die.values.items[index];
return .{ .attr = attr, .bytes = value };
}
pub const Index = u32;
};
pub const DieValue = struct {
attr: Attr,
bytes: []const u8,
pub fn getFlag(value: DieValue) ?bool {
return switch (value.attr.form) {
dwarf.FORM.flag => value.bytes[0] == 1,
dwarf.FORM.flag_present => true,
else => null,
};
}
pub fn getString(value: DieValue, format: Format, ctx: DwarfInfo) ?[:0]const u8 {
switch (value.attr.form) {
dwarf.FORM.string => {
return mem.sliceTo(@as([*:0]const u8, @ptrCast(value.bytes.ptr)), 0);
},
dwarf.FORM.strp => {
const off = switch (format) {
.dwarf64 => mem.readInt(u64, value.bytes[0..8], .little),
.dwarf32 => mem.readInt(u32, value.bytes[0..4], .little),
};
return ctx.getString(off);
},
else => return null,
}
}
pub fn getSecOffset(value: DieValue, format: Format) ?u64 {
return switch (value.attr.form) {
dwarf.FORM.sec_offset => switch (format) {
.dwarf32 => mem.readInt(u32, value.bytes[0..4], .little),
.dwarf64 => mem.readInt(u64, value.bytes[0..8], .little),
},
else => null,
};
}
pub fn getConstant(value: DieValue) !?i128 {
var stream = std.io.fixedBufferStream(value.bytes);
const reader = stream.reader();
return switch (value.attr.form) {
dwarf.FORM.data1 => value.bytes[0],
dwarf.FORM.data2 => mem.readInt(u16, value.bytes[0..2], .little),
dwarf.FORM.data4 => mem.readInt(u32, value.bytes[0..4], .little),
dwarf.FORM.data8 => mem.readInt(u64, value.bytes[0..8], .little),
dwarf.FORM.udata => try leb.readULEB128(u64, reader),
dwarf.FORM.sdata => try leb.readILEB128(i64, reader),
else => null,
};
}
pub fn getReference(value: DieValue, format: Format) !?u64 {
var stream = std.io.fixedBufferStream(value.bytes);
const reader = stream.reader();
return switch (value.attr.form) {
dwarf.FORM.ref1 => value.bytes[0],
dwarf.FORM.ref2 => mem.readInt(u16, value.bytes[0..2], .little),
dwarf.FORM.ref4 => mem.readInt(u32, value.bytes[0..4], .little),
dwarf.FORM.ref8 => mem.readInt(u64, value.bytes[0..8], .little),
dwarf.FORM.ref_udata => try leb.readULEB128(u64, reader),
dwarf.FORM.ref_addr => switch (format) {
.dwarf32 => mem.readInt(u32, value.bytes[0..4], .little),
.dwarf64 => mem.readInt(u64, value.bytes[0..8], .little),
},
else => null,
};
}
pub fn getAddr(value: DieValue, header: CompileUnitHeader) ?u64 {
return switch (value.attr.form) {
dwarf.FORM.addr => switch (header.address_size) {
1 => value.bytes[0],
2 => mem.readInt(u16, value.bytes[0..2], .little),
4 => mem.readInt(u32, value.bytes[0..4], .little),
8 => mem.readInt(u64, value.bytes[0..8], .little),
else => null,
},
else => null,
};
}
pub fn getExprloc(value: DieValue) !?[]const u8 {
if (value.attr.form != dwarf.FORM.exprloc) return null;
var stream = std.io.fixedBufferStream(value.bytes);
var creader = std.io.countingReader(stream.reader());
const reader = creader.reader();
const expr_len = try leb.readULEB128(u64, reader);
return value.bytes[creader.bytes_read..][0..expr_len];
}
};
pub const Format = enum {
dwarf32,
dwarf64,
};
const std = @import("std");
const assert = std.debug.assert; const assert = std.debug.assert;
const dwarf = std.dwarf; const dwarf = std.dwarf;
const leb = std.leb; const leb = std.leb;
const log = std.log.scoped(.macho); const log = std.log.scoped(.link);
const math = std.math;
const mem = std.mem; const mem = std.mem;
const std = @import("std");
const trace = @import("../tracy.zig").trace;
const Allocator = mem.Allocator; const Allocator = mem.Allocator;
pub const AbbrevLookupTable = std.AutoHashMap(u64, struct { pos: usize, len: usize }); const DwarfInfo = @This();
pub const SubprogramLookupByName = std.StringHashMap(struct { addr: u64, size: u64 }); const MachO = @import("../MachO.zig");

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,249 @@
index: File.Index,
sections: std.MultiArrayList(Section) = .{},
atoms: std.ArrayListUnmanaged(Atom.Index) = .{},
symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
objc_methnames: std.ArrayListUnmanaged(u8) = .{},
objc_selrefs: [@sizeOf(u64)]u8 = [_]u8{0} ** @sizeOf(u64),
output_symtab_ctx: MachO.SymtabCtx = .{},
pub fn deinit(self: *InternalObject, allocator: Allocator) void {
for (self.sections.items(.relocs)) |*relocs| {
relocs.deinit(allocator);
}
self.sections.deinit(allocator);
self.atoms.deinit(allocator);
self.symbols.deinit(allocator);
self.objc_methnames.deinit(allocator);
}
pub fn addSymbol(self: *InternalObject, name: [:0]const u8, macho_file: *MachO) !Symbol.Index {
const gpa = macho_file.base.allocator;
try self.symbols.ensureUnusedCapacity(gpa, 1);
const off = try macho_file.string_intern.insert(gpa, name);
const gop = try macho_file.getOrCreateGlobal(off);
self.symbols.addOneAssumeCapacity().* = gop.index;
const sym = macho_file.getSymbol(gop.index);
sym.* = .{ .name = off, .file = self.index };
return gop.index;
}
/// Creates a fake input sections __TEXT,__objc_methname and __DATA,__objc_selrefs.
pub fn addObjcMsgsendSections(self: *InternalObject, sym_name: []const u8, macho_file: *MachO) !u32 {
const methname_atom_index = try self.addObjcMethnameSection(sym_name, macho_file);
return try self.addObjcSelrefsSection(sym_name, methname_atom_index, macho_file);
}
fn addObjcMethnameSection(self: *InternalObject, methname: []const u8, macho_file: *MachO) !Atom.Index {
const gpa = macho_file.base.allocator;
const atom_index = try macho_file.addAtom();
try self.atoms.append(gpa, atom_index);
const name = try std.fmt.allocPrintZ(gpa, "__TEXT$__objc_methname${s}", .{methname});
defer gpa.free(name);
const atom = macho_file.getAtom(atom_index).?;
atom.atom_index = atom_index;
atom.name = try macho_file.string_intern.insert(gpa, name);
atom.file = self.index;
atom.size = methname.len + 1;
atom.alignment = 0;
const n_sect = try self.addSection(gpa, "__TEXT", "__objc_methname");
const sect = &self.sections.items(.header)[n_sect];
sect.flags = macho.S_CSTRING_LITERALS;
sect.size = atom.size;
sect.@"align" = 0;
atom.n_sect = n_sect;
self.sections.items(.extra)[n_sect].is_objc_methname = true;
sect.offset = @intCast(self.objc_methnames.items.len);
try self.objc_methnames.ensureUnusedCapacity(gpa, methname.len + 1);
self.objc_methnames.writer(gpa).print("{s}\x00", .{methname}) catch unreachable;
return atom_index;
}
fn addObjcSelrefsSection(
self: *InternalObject,
methname: []const u8,
methname_atom_index: Atom.Index,
macho_file: *MachO,
) !Atom.Index {
const gpa = macho_file.base.allocator;
const atom_index = try macho_file.addAtom();
try self.atoms.append(gpa, atom_index);
const name = try std.fmt.allocPrintZ(gpa, "__DATA$__objc_selrefs${s}", .{methname});
defer gpa.free(name);
const atom = macho_file.getAtom(atom_index).?;
atom.atom_index = atom_index;
atom.name = try macho_file.string_intern.insert(gpa, name);
atom.file = self.index;
atom.size = @sizeOf(u64);
atom.alignment = 3;
const n_sect = try self.addSection(gpa, "__DATA", "__objc_selrefs");
const sect = &self.sections.items(.header)[n_sect];
sect.flags = macho.S_LITERAL_POINTERS | macho.S_ATTR_NO_DEAD_STRIP;
sect.offset = 0;
sect.size = atom.size;
sect.@"align" = 3;
atom.n_sect = n_sect;
self.sections.items(.extra)[n_sect].is_objc_selref = true;
const relocs = &self.sections.items(.relocs)[n_sect];
try relocs.ensureUnusedCapacity(gpa, 1);
relocs.appendAssumeCapacity(.{
.tag = .local,
.offset = 0,
.target = methname_atom_index,
.addend = 0,
.type = .unsigned,
.meta = .{
.pcrel = false,
.length = 3,
.symbolnum = 0, // Only used when synthesising unwind records so can be anything
.has_subtractor = false,
},
});
atom.relocs = .{ .pos = 0, .len = 1 };
return atom_index;
}
pub fn calcSymtabSize(self: *InternalObject, macho_file: *MachO) !void {
for (self.symbols.items) |sym_index| {
const sym = macho_file.getSymbol(sym_index);
if (sym.getFile(macho_file)) |file| if (file.getIndex() != self.index) continue;
sym.flags.output_symtab = true;
if (sym.isLocal()) {
try sym.addExtra(.{ .symtab = self.output_symtab_ctx.nlocals }, macho_file);
self.output_symtab_ctx.nlocals += 1;
} else if (sym.flags.@"export") {
try sym.addExtra(.{ .symtab = self.output_symtab_ctx.nexports }, macho_file);
self.output_symtab_ctx.nexports += 1;
} else {
assert(sym.flags.import);
try sym.addExtra(.{ .symtab = self.output_symtab_ctx.nimports }, macho_file);
self.output_symtab_ctx.nimports += 1;
}
self.output_symtab_ctx.strsize += @as(u32, @intCast(sym.getName(macho_file).len + 1));
}
}
pub fn writeSymtab(self: InternalObject, macho_file: *MachO) void {
for (self.symbols.items) |sym_index| {
const sym = macho_file.getSymbol(sym_index);
if (sym.getFile(macho_file)) |file| if (file.getIndex() != self.index) continue;
const idx = sym.getOutputSymtabIndex(macho_file) orelse continue;
const n_strx = @as(u32, @intCast(macho_file.strtab.items.len));
macho_file.strtab.appendSliceAssumeCapacity(sym.getName(macho_file));
macho_file.strtab.appendAssumeCapacity(0);
const out_sym = &macho_file.symtab.items[idx];
out_sym.n_strx = n_strx;
sym.setOutputSym(macho_file, out_sym);
}
}
fn addSection(self: *InternalObject, allocator: Allocator, segname: []const u8, sectname: []const u8) !u32 {
const n_sect = @as(u32, @intCast(try self.sections.addOne(allocator)));
self.sections.set(n_sect, .{
.header = .{
.sectname = MachO.makeStaticString(sectname),
.segname = MachO.makeStaticString(segname),
},
});
return n_sect;
}
pub fn getSectionData(self: *const InternalObject, index: u32) []const u8 {
const slice = self.sections.slice();
assert(index < slice.items(.header).len);
const sect = slice.items(.header)[index];
const extra = slice.items(.extra)[index];
if (extra.is_objc_methname) {
return self.objc_methnames.items[sect.offset..][0..sect.size];
} else if (extra.is_objc_selref) {
return &self.objc_selrefs;
} else @panic("ref to non-existent section");
}
pub fn asFile(self: *InternalObject) File {
return .{ .internal = self };
}
const FormatContext = struct {
self: *InternalObject,
macho_file: *MachO,
};
pub fn fmtAtoms(self: *InternalObject, macho_file: *MachO) std.fmt.Formatter(formatAtoms) {
return .{ .data = .{
.self = self,
.macho_file = macho_file,
} };
}
fn formatAtoms(
ctx: FormatContext,
comptime unused_fmt_string: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = unused_fmt_string;
_ = options;
try writer.writeAll(" atoms\n");
for (ctx.self.atoms.items) |atom_index| {
const atom = ctx.macho_file.getAtom(atom_index).?;
try writer.print(" {}\n", .{atom.fmt(ctx.macho_file)});
}
}
pub fn fmtSymtab(self: *InternalObject, macho_file: *MachO) std.fmt.Formatter(formatSymtab) {
return .{ .data = .{
.self = self,
.macho_file = macho_file,
} };
}
fn formatSymtab(
ctx: FormatContext,
comptime unused_fmt_string: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = unused_fmt_string;
_ = options;
try writer.writeAll(" symbols\n");
for (ctx.self.symbols.items) |index| {
const global = ctx.macho_file.getSymbol(index);
try writer.print(" {}\n", .{global.fmt(ctx.macho_file)});
}
}
const Section = struct {
header: macho.section_64,
relocs: std.ArrayListUnmanaged(Relocation) = .{},
extra: Extra = .{},
const Extra = packed struct {
is_objc_methname: bool = false,
is_objc_selref: bool = false,
};
};
const assert = std.debug.assert;
const macho = std.macho;
const mem = std.mem;
const std = @import("std");
const Allocator = std.mem.Allocator;
const Atom = @import("Atom.zig");
const File = @import("file.zig").File;
const InternalObject = @This();
const MachO = @import("../MachO.zig");
const Object = @import("Object.zig");
const Relocation = @import("Relocation.zig");
const Symbol = @import("Symbol.zig");

File diff suppressed because it is too large Load Diff

View File

@ -1,235 +1,62 @@
//! Relocation used by the self-hosted backends to instruct the linker where and how to tag: enum { @"extern", local },
//! fixup the values when flushing the contents to file and/or memory.
type: Type,
target: SymbolWithLoc,
offset: u32, offset: u32,
target: u32,
addend: i64, addend: i64,
pcrel: bool, type: Type,
length: u2, meta: packed struct {
dirty: bool = true, pcrel: bool,
has_subtractor: bool,
length: u2,
symbolnum: u24,
},
pub const Type = enum { pub fn getTargetSymbol(rel: Relocation, macho_file: *MachO) *Symbol {
// x86, x86_64 assert(rel.tag == .@"extern");
/// RIP-relative displacement to a GOT pointer return macho_file.getSymbol(rel.target);
got,
/// RIP-relative displacement
signed,
/// RIP-relative displacement to a TLV thunk
tlv,
// aarch64
/// PC-relative distance to target page in GOT section
got_page,
/// Offset to a GOT pointer relative to the start of a page in GOT section
got_pageoff,
/// PC-relative distance to target page in a section
page,
/// Offset to a pointer relative to the start of a page in a section
pageoff,
// common
/// PC/RIP-relative displacement B/BL/CALL
branch,
/// Absolute pointer value
unsigned,
/// Relative offset to TLV initializer
tlv_initializer,
};
/// Returns true if and only if the reloc can be resolved.
pub fn isResolvable(self: Relocation, macho_file: *MachO) bool {
_ = self.getTargetBaseAddress(macho_file) orelse return false;
return true;
} }
pub fn isGotIndirection(self: Relocation) bool { pub fn getTargetAtom(rel: Relocation, macho_file: *MachO) *Atom {
return switch (self.type) { assert(rel.tag == .local);
.got, .got_page, .got_pageoff => true, return macho_file.getAtom(rel.target).?;
else => false, }
pub fn getTargetAddress(rel: Relocation, macho_file: *MachO) u64 {
return switch (rel.tag) {
.local => rel.getTargetAtom(macho_file).value,
.@"extern" => rel.getTargetSymbol(macho_file).getAddress(.{}, macho_file),
}; };
} }
pub fn isStubTrampoline(self: Relocation, macho_file: *MachO) bool { pub fn getGotTargetAddress(rel: Relocation, macho_file: *MachO) u64 {
return switch (self.type) { return switch (rel.tag) {
.branch => macho_file.getSymbol(self.target).undf(), .local => 0,
else => false, .@"extern" => rel.getTargetSymbol(macho_file).getGotAddress(macho_file),
}; };
} }
pub fn getTargetBaseAddress(self: Relocation, macho_file: *MachO) ?u64 { pub fn getRelocAddend(rel: Relocation, cpu_arch: std.Target.Cpu.Arch) i64 {
const target = macho_file.base.comp.root_mod.resolved_target.result; const addend: i64 = switch (rel.type) {
if (self.isStubTrampoline(macho_file)) { .signed => 0,
const index = macho_file.stub_table.lookup.get(self.target) orelse return null; .signed1 => -1,
const header = macho_file.sections.items(.header)[macho_file.stubs_section_index.?]; .signed2 => -2,
return header.addr + .signed4 => -4,
index * @import("stubs.zig").stubSize(target.cpu.arch); else => 0,
} };
switch (self.type) { return switch (cpu_arch) {
.got, .got_page, .got_pageoff => { .x86_64 => if (rel.meta.pcrel) addend - 4 else addend,
const got_index = macho_file.got_table.lookup.get(self.target) orelse return null; else => addend,
const header = macho_file.sections.items(.header)[macho_file.got_section_index.?];
return header.addr + got_index * @sizeOf(u64);
},
.tlv => {
const atom_index = macho_file.tlv_table.get(self.target) orelse return null;
const atom = macho_file.getAtom(atom_index);
return atom.getSymbol(macho_file).n_value;
},
else => {
const target_atom_index = macho_file.getAtomIndexForSymbol(self.target) orelse return null;
const target_atom = macho_file.getAtom(target_atom_index);
return target_atom.getSymbol(macho_file).n_value;
},
}
}
pub fn resolve(self: Relocation, macho_file: *MachO, atom_index: Atom.Index, code: []u8) void {
const target = macho_file.base.comp.root_mod.resolved_target.result;
const arch = target.cpu.arch;
const atom = macho_file.getAtom(atom_index);
const source_sym = atom.getSymbol(macho_file);
const source_addr = source_sym.n_value + self.offset;
const target_base_addr = self.getTargetBaseAddress(macho_file).?; // Oops, you didn't check if the relocation can be resolved with isResolvable().
const target_addr: i64 = switch (self.type) {
.tlv_initializer => blk: {
assert(self.addend == 0); // Addend here makes no sense.
const header = macho_file.sections.items(.header)[macho_file.thread_data_section_index.?];
break :blk @as(i64, @intCast(target_base_addr - header.addr));
},
else => @as(i64, @intCast(target_base_addr)) + self.addend,
}; };
relocs_log.debug(" ({x}: [() => 0x{x} ({s})) ({s})", .{
source_addr,
target_addr,
macho_file.getSymbolName(self.target),
@tagName(self.type),
});
switch (arch) {
.aarch64 => self.resolveAarch64(source_addr, target_addr, code),
.x86_64 => self.resolveX8664(source_addr, target_addr, code),
else => unreachable,
}
} }
fn resolveAarch64(self: Relocation, source_addr: u64, target_addr: i64, code: []u8) void { pub fn lessThan(ctx: void, lhs: Relocation, rhs: Relocation) bool {
var buffer = code[self.offset..]; _ = ctx;
switch (self.type) { return lhs.offset < rhs.offset;
.branch => {
const displacement = math.cast(
i28,
@as(i64, @intCast(target_addr)) - @as(i64, @intCast(source_addr)),
) orelse unreachable; // TODO codegen should never allow for jump larger than i28 displacement
var inst = aarch64.Instruction{
.unconditional_branch_immediate = mem.bytesToValue(meta.TagPayload(
aarch64.Instruction,
aarch64.Instruction.unconditional_branch_immediate,
), buffer[0..4]),
};
inst.unconditional_branch_immediate.imm26 = @as(u26, @truncate(@as(u28, @bitCast(displacement >> 2))));
mem.writeInt(u32, buffer[0..4], inst.toU32(), .little);
},
.page, .got_page => {
const source_page = @as(i32, @intCast(source_addr >> 12));
const target_page = @as(i32, @intCast(target_addr >> 12));
const pages = @as(u21, @bitCast(@as(i21, @intCast(target_page - source_page))));
var inst = aarch64.Instruction{
.pc_relative_address = mem.bytesToValue(meta.TagPayload(
aarch64.Instruction,
aarch64.Instruction.pc_relative_address,
), buffer[0..4]),
};
inst.pc_relative_address.immhi = @as(u19, @truncate(pages >> 2));
inst.pc_relative_address.immlo = @as(u2, @truncate(pages));
mem.writeInt(u32, buffer[0..4], inst.toU32(), .little);
},
.pageoff, .got_pageoff => {
const narrowed = @as(u12, @truncate(@as(u64, @intCast(target_addr))));
if (isArithmeticOp(buffer[0..4])) {
var inst = aarch64.Instruction{
.add_subtract_immediate = mem.bytesToValue(meta.TagPayload(
aarch64.Instruction,
aarch64.Instruction.add_subtract_immediate,
), buffer[0..4]),
};
inst.add_subtract_immediate.imm12 = narrowed;
mem.writeInt(u32, buffer[0..4], inst.toU32(), .little);
} else {
var inst = aarch64.Instruction{
.load_store_register = mem.bytesToValue(meta.TagPayload(
aarch64.Instruction,
aarch64.Instruction.load_store_register,
), buffer[0..4]),
};
const offset: u12 = blk: {
if (inst.load_store_register.size == 0) {
if (inst.load_store_register.v == 1) {
// 128-bit SIMD is scaled by 16.
break :blk @divExact(narrowed, 16);
}
// Otherwise, 8-bit SIMD or ldrb.
break :blk narrowed;
} else {
const denom: u4 = math.powi(u4, 2, inst.load_store_register.size) catch unreachable;
break :blk @divExact(narrowed, denom);
}
};
inst.load_store_register.offset = offset;
mem.writeInt(u32, buffer[0..4], inst.toU32(), .little);
}
},
.tlv_initializer, .unsigned => switch (self.length) {
2 => mem.writeInt(u32, buffer[0..4], @as(u32, @truncate(@as(u64, @bitCast(target_addr)))), .little),
3 => mem.writeInt(u64, buffer[0..8], @as(u64, @bitCast(target_addr)), .little),
else => unreachable,
},
.got, .signed, .tlv => unreachable, // Invalid target architecture.
}
} }
fn resolveX8664(self: Relocation, source_addr: u64, target_addr: i64, code: []u8) void { pub fn calcNumberOfPages(saddr: u64, taddr: u64) error{Overflow}!i21 {
switch (self.type) { const spage = math.cast(i32, saddr >> 12) orelse return error.Overflow;
.branch, .got, .tlv, .signed => { const tpage = math.cast(i32, taddr >> 12) orelse return error.Overflow;
const displacement = @as(i32, @intCast(@as(i64, @intCast(target_addr)) - @as(i64, @intCast(source_addr)) - 4)); const pages = math.cast(i21, tpage - spage) orelse return error.Overflow;
mem.writeInt(u32, code[self.offset..][0..4], @as(u32, @bitCast(displacement)), .little);
},
.tlv_initializer, .unsigned => {
switch (self.length) {
2 => {
mem.writeInt(u32, code[self.offset..][0..4], @as(u32, @truncate(@as(u64, @bitCast(target_addr)))), .little);
},
3 => {
mem.writeInt(u64, code[self.offset..][0..8], @as(u64, @bitCast(target_addr)), .little);
},
else => unreachable,
}
},
.got_page, .got_pageoff, .page, .pageoff => unreachable, // Invalid target architecture.
}
}
pub inline fn isArithmeticOp(inst: *const [4]u8) bool {
const group_decode = @as(u5, @truncate(inst[3]));
return ((group_decode >> 2) == 4);
}
pub fn calcPcRelativeDisplacementX86(source_addr: u64, target_addr: u64, correction: u3) error{Overflow}!i32 {
const disp = @as(i64, @intCast(target_addr)) - @as(i64, @intCast(source_addr + 4 + correction));
return math.cast(i32, disp) orelse error.Overflow;
}
pub fn calcPcRelativeDisplacementArm64(source_addr: u64, target_addr: u64) error{Overflow}!i28 {
const disp = @as(i64, @intCast(target_addr)) - @as(i64, @intCast(source_addr));
return math.cast(i28, disp) orelse error.Overflow;
}
pub fn calcNumberOfPages(source_addr: u64, target_addr: u64) i21 {
const source_page = @as(i32, @intCast(source_addr >> 12));
const target_page = @as(i32, @intCast(target_addr >> 12));
const pages = @as(i21, @intCast(target_page - source_page));
return pages; return pages;
} }
@ -242,8 +69,8 @@ pub const PageOffsetInstKind = enum {
load_store_128, load_store_128,
}; };
pub fn calcPageOffset(target_addr: u64, kind: PageOffsetInstKind) !u12 { pub fn calcPageOffset(taddr: u64, kind: PageOffsetInstKind) !u12 {
const narrowed = @as(u12, @truncate(target_addr)); const narrowed = @as(u12, @truncate(taddr));
return switch (kind) { return switch (kind) {
.arithmetic, .load_store_8 => narrowed, .arithmetic, .load_store_8 => narrowed,
.load_store_16 => try math.divExact(u12, narrowed, 2), .load_store_16 => try math.divExact(u12, narrowed, 2),
@ -253,17 +80,57 @@ pub fn calcPageOffset(target_addr: u64, kind: PageOffsetInstKind) !u12 {
}; };
} }
const Relocation = @This(); pub inline fn isArithmeticOp(inst: *const [4]u8) bool {
const group_decode = @as(u5, @truncate(inst[3]));
return ((group_decode >> 2) == 4);
}
pub const Type = enum {
// x86_64
/// RIP-relative displacement (X86_64_RELOC_SIGNED)
signed,
/// RIP-relative displacement (X86_64_RELOC_SIGNED_1)
signed1,
/// RIP-relative displacement (X86_64_RELOC_SIGNED_2)
signed2,
/// RIP-relative displacement (X86_64_RELOC_SIGNED_4)
signed4,
/// RIP-relative GOT load (X86_64_RELOC_GOT_LOAD)
got_load,
/// RIP-relative TLV load (X86_64_RELOC_TLV)
tlv,
// arm64
/// PC-relative load (distance to page, ARM64_RELOC_PAGE21)
page,
/// Non-PC-relative offset to symbol (ARM64_RELOC_PAGEOFF12)
pageoff,
/// PC-relative GOT load (distance to page, ARM64_RELOC_GOT_LOAD_PAGE21)
got_load_page,
/// Non-PC-relative offset to GOT slot (ARM64_RELOC_GOT_LOAD_PAGEOFF12)
got_load_pageoff,
/// PC-relative TLV load (distance to page, ARM64_RELOC_TLVP_LOAD_PAGE21)
tlvp_page,
/// Non-PC-relative offset to TLV slot (ARM64_RELOC_TLVP_LOAD_PAGEOFF12)
tlvp_pageoff,
// common
/// PC-relative call/bl/b (X86_64_RELOC_BRANCH or ARM64_RELOC_BRANCH26)
branch,
/// PC-relative displacement to GOT pointer (X86_64_RELOC_GOT or ARM64_RELOC_POINTER_TO_GOT)
got,
/// Absolute subtractor value (X86_64_RELOC_SUBTRACTOR or ARM64_RELOC_SUBTRACTOR)
subtractor,
/// Absolute relocation (X86_64_RELOC_UNSIGNED or ARM64_RELOC_UNSIGNED)
unsigned,
};
const std = @import("std");
const aarch64 = @import("../../arch/aarch64/bits.zig");
const assert = std.debug.assert; const assert = std.debug.assert;
const relocs_log = std.log.scoped(.link_relocs);
const macho = std.macho; const macho = std.macho;
const math = std.math; const math = std.math;
const mem = std.mem; const std = @import("std");
const meta = std.meta;
const Atom = @import("Atom.zig"); const Atom = @import("Atom.zig");
const MachO = @import("../MachO.zig"); const MachO = @import("../MachO.zig");
const SymbolWithLoc = MachO.SymbolWithLoc; const Relocation = @This();
const Symbol = @import("Symbol.zig");

383
src/link/MachO/Symbol.zig Normal file
View File

@ -0,0 +1,383 @@
//! Represents a defined symbol.
/// Allocated address value of this symbol.
value: u64 = 0,
/// Offset into the linker's intern table.
name: u32 = 0,
/// File where this symbol is defined.
file: File.Index = 0,
/// Atom containing this symbol if any.
/// Index of 0 means there is no associated atom with this symbol.
/// Use `getAtom` to get the pointer to the atom.
atom: Atom.Index = 0,
/// Assigned output section index for this atom.
out_n_sect: u16 = 0,
/// Index of the source nlist this symbol references.
/// Use `getNlist` to pull the nlist from the relevant file.
nlist_idx: u32 = 0,
/// Misc flags for the symbol packaged as packed struct for compression.
flags: Flags = .{},
visibility: Visibility = .local,
extra: u32 = 0,
pub fn isLocal(symbol: Symbol) bool {
return !(symbol.flags.import or symbol.flags.@"export");
}
pub fn isSymbolStab(symbol: Symbol, macho_file: *MachO) bool {
const file = symbol.getFile(macho_file) orelse return false;
return switch (file) {
.object => symbol.getNlist(macho_file).stab(),
else => false,
};
}
pub fn isTlvInit(symbol: Symbol, macho_file: *MachO) bool {
const name = symbol.getName(macho_file);
return std.mem.indexOf(u8, name, "$tlv$init") != null;
}
pub fn weakRef(symbol: Symbol, macho_file: *MachO) bool {
const file = symbol.getFile(macho_file).?;
const is_dylib_weak = switch (file) {
.dylib => |x| x.weak,
else => false,
};
return is_dylib_weak or symbol.flags.weak_ref;
}
pub fn getName(symbol: Symbol, macho_file: *MachO) [:0]const u8 {
return macho_file.string_intern.getAssumeExists(symbol.name);
}
pub fn getAtom(symbol: Symbol, macho_file: *MachO) ?*Atom {
return macho_file.getAtom(symbol.atom);
}
pub fn getFile(symbol: Symbol, macho_file: *MachO) ?File {
return macho_file.getFile(symbol.file);
}
/// Asserts file is an object.
pub fn getNlist(symbol: Symbol, macho_file: *MachO) macho.nlist_64 {
const file = symbol.getFile(macho_file).?;
return switch (file) {
.object => |x| x.symtab.items(.nlist)[symbol.nlist_idx],
else => unreachable,
};
}
pub fn getSize(symbol: Symbol, macho_file: *MachO) u64 {
const file = symbol.getFile(macho_file).?;
assert(file == .object);
return file.object.symtab.items(.size)[symbol.nlist_idx];
}
pub fn getDylibOrdinal(symbol: Symbol, macho_file: *MachO) ?u16 {
assert(symbol.flags.import);
const file = symbol.getFile(macho_file) orelse return null;
return switch (file) {
.dylib => |x| x.ordinal,
else => null,
};
}
pub fn getSymbolRank(symbol: Symbol, macho_file: *MachO) u32 {
const file = symbol.getFile(macho_file) orelse return std.math.maxInt(u32);
const in_archive = switch (file) {
.object => |x| !x.alive,
else => false,
};
return file.getSymbolRank(.{
.archive = in_archive,
.weak = symbol.flags.weak,
.tentative = symbol.flags.tentative,
});
}
pub fn getAddress(symbol: Symbol, opts: struct {
stubs: bool = true,
}, macho_file: *MachO) u64 {
if (opts.stubs) {
if (symbol.flags.stubs) {
return symbol.getStubsAddress(macho_file);
} else if (symbol.flags.objc_stubs) {
return symbol.getObjcStubsAddress(macho_file);
}
}
if (symbol.getAtom(macho_file)) |atom| return atom.value + symbol.value;
return symbol.value;
}
pub fn getGotAddress(symbol: Symbol, macho_file: *MachO) u64 {
if (!symbol.flags.got) return 0;
const extra = symbol.getExtra(macho_file).?;
return macho_file.got.getAddress(extra.got, macho_file);
}
pub fn getStubsAddress(symbol: Symbol, macho_file: *MachO) u64 {
if (!symbol.flags.stubs) return 0;
const extra = symbol.getExtra(macho_file).?;
return macho_file.stubs.getAddress(extra.stubs, macho_file);
}
pub fn getObjcStubsAddress(symbol: Symbol, macho_file: *MachO) u64 {
if (!symbol.flags.objc_stubs) return 0;
const extra = symbol.getExtra(macho_file).?;
return macho_file.objc_stubs.getAddress(extra.objc_stubs, macho_file);
}
pub fn getObjcSelrefsAddress(symbol: Symbol, macho_file: *MachO) u64 {
if (!symbol.flags.objc_stubs) return 0;
const extra = symbol.getExtra(macho_file).?;
const atom = macho_file.getAtom(extra.objc_selrefs).?;
assert(atom.flags.alive);
return atom.value;
}
pub fn getTlvPtrAddress(symbol: Symbol, macho_file: *MachO) u64 {
if (!symbol.flags.tlv_ptr) return 0;
const extra = symbol.getExtra(macho_file).?;
return macho_file.tlv_ptr.getAddress(extra.tlv_ptr, macho_file);
}
pub fn getOutputSymtabIndex(symbol: Symbol, macho_file: *MachO) ?u32 {
if (!symbol.flags.output_symtab) return null;
assert(!symbol.isSymbolStab(macho_file));
const file = symbol.getFile(macho_file).?;
const symtab_ctx = switch (file) {
inline else => |x| x.output_symtab_ctx,
};
var idx = symbol.getExtra(macho_file).?.symtab;
if (symbol.isLocal()) {
idx += symtab_ctx.ilocal;
} else if (symbol.flags.@"export") {
idx += symtab_ctx.iexport;
} else {
assert(symbol.flags.import);
idx += symtab_ctx.iimport;
}
return idx;
}
const AddExtraOpts = struct {
got: ?u32 = null,
stubs: ?u32 = null,
objc_stubs: ?u32 = null,
objc_selrefs: ?u32 = null,
tlv_ptr: ?u32 = null,
symtab: ?u32 = null,
};
pub fn addExtra(symbol: *Symbol, opts: AddExtraOpts, macho_file: *MachO) !void {
if (symbol.getExtra(macho_file) == null) {
symbol.extra = try macho_file.addSymbolExtra(.{});
}
var extra = symbol.getExtra(macho_file).?;
inline for (@typeInfo(@TypeOf(opts)).Struct.fields) |field| {
if (@field(opts, field.name)) |x| {
@field(extra, field.name) = x;
}
}
symbol.setExtra(extra, macho_file);
}
pub inline fn getExtra(symbol: Symbol, macho_file: *MachO) ?Extra {
return macho_file.getSymbolExtra(symbol.extra);
}
pub inline fn setExtra(symbol: Symbol, extra: Extra, macho_file: *MachO) void {
macho_file.setSymbolExtra(symbol.extra, extra);
}
pub fn setOutputSym(symbol: Symbol, macho_file: *MachO, out: *macho.nlist_64) void {
if (symbol.isLocal()) {
out.n_type = if (symbol.flags.abs) macho.N_ABS else macho.N_SECT;
out.n_sect = if (symbol.flags.abs) 0 else @intCast(symbol.out_n_sect + 1);
out.n_desc = 0;
out.n_value = symbol.getAddress(.{}, macho_file);
switch (symbol.visibility) {
.hidden => out.n_type |= macho.N_PEXT,
else => {},
}
} else if (symbol.flags.@"export") {
assert(symbol.visibility == .global);
out.n_type = macho.N_EXT;
out.n_type |= if (symbol.flags.abs) macho.N_ABS else macho.N_SECT;
out.n_sect = if (symbol.flags.abs) 0 else @intCast(symbol.out_n_sect + 1);
out.n_value = symbol.getAddress(.{}, macho_file);
out.n_desc = 0;
if (symbol.flags.weak) {
out.n_desc |= macho.N_WEAK_DEF;
}
if (symbol.flags.dyn_ref) {
out.n_desc |= macho.REFERENCED_DYNAMICALLY;
}
} else {
assert(symbol.visibility == .global);
out.n_type = macho.N_EXT;
out.n_sect = 0;
out.n_value = 0;
out.n_desc = 0;
const ord: u16 = if (macho_file.options.namespace == .flat)
@as(u8, @bitCast(macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP))
else if (symbol.getDylibOrdinal(macho_file)) |ord|
ord
else
macho.BIND_SPECIAL_DYLIB_SELF;
out.n_desc = macho.N_SYMBOL_RESOLVER * ord;
if (symbol.flags.weak) {
out.n_desc |= macho.N_WEAK_DEF;
}
if (symbol.weakRef(macho_file)) {
out.n_desc |= macho.N_WEAK_REF;
}
}
}
pub fn format(
symbol: Symbol,
comptime unused_fmt_string: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = symbol;
_ = unused_fmt_string;
_ = options;
_ = writer;
@compileError("do not format symbols directly");
}
const FormatContext = struct {
symbol: Symbol,
macho_file: *MachO,
};
pub fn fmt(symbol: Symbol, macho_file: *MachO) std.fmt.Formatter(format2) {
return .{ .data = .{
.symbol = symbol,
.macho_file = macho_file,
} };
}
fn format2(
ctx: FormatContext,
comptime unused_fmt_string: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = options;
_ = unused_fmt_string;
const symbol = ctx.symbol;
try writer.print("%{d} : {s} : @{x}", .{
symbol.nlist_idx,
symbol.getName(ctx.macho_file),
symbol.getAddress(.{}, ctx.macho_file),
});
if (symbol.getFile(ctx.macho_file)) |file| {
if (symbol.out_n_sect != 0) {
try writer.print(" : sect({d})", .{symbol.out_n_sect});
}
if (symbol.getAtom(ctx.macho_file)) |atom| {
try writer.print(" : atom({d})", .{atom.atom_index});
}
var buf: [2]u8 = .{'_'} ** 2;
if (symbol.flags.@"export") buf[0] = 'E';
if (symbol.flags.import) buf[1] = 'I';
try writer.print(" : {s}", .{&buf});
if (symbol.flags.weak) try writer.writeAll(" : weak");
if (symbol.isSymbolStab(ctx.macho_file)) try writer.writeAll(" : stab");
switch (file) {
.internal => |x| try writer.print(" : internal({d})", .{x.index}),
.object => |x| try writer.print(" : object({d})", .{x.index}),
.dylib => |x| try writer.print(" : dylib({d})", .{x.index}),
}
} else try writer.writeAll(" : unresolved");
}
pub const Flags = packed struct {
/// Whether the symbol is imported at runtime.
import: bool = false,
/// Whether the symbol is exported at runtime.
@"export": bool = false,
/// Whether this symbol is weak.
weak: bool = false,
/// Whether this symbol is weakly referenced.
weak_ref: bool = false,
/// Whether this symbol is dynamically referenced.
dyn_ref: bool = false,
/// Whether this symbol was marked as N_NO_DEAD_STRIP.
no_dead_strip: bool = false,
/// Whether this symbol can be interposed at runtime.
interposable: bool = false,
/// Whether this symbol is absolute.
abs: bool = false,
/// Whether this symbol is a tentative definition.
tentative: bool = false,
/// Whether this symbol is a thread-local variable.
tlv: bool = false,
/// Whether the symbol makes into the output symtab or not.
output_symtab: bool = false,
/// Whether the symbol contains __got indirection.
got: bool = false,
/// Whether the symbols contains __stubs indirection.
stubs: bool = false,
/// Whether the symbol has a TLV pointer.
tlv_ptr: bool = false,
/// Whether the symbol contains __objc_stubs indirection.
objc_stubs: bool = false,
};
pub const Visibility = enum {
global,
hidden,
local,
};
pub const Extra = struct {
got: u32 = 0,
stubs: u32 = 0,
objc_stubs: u32 = 0,
objc_selrefs: u32 = 0,
tlv_ptr: u32 = 0,
symtab: u32 = 0,
};
pub const Index = u32;
const assert = std.debug.assert;
const macho = std.macho;
const std = @import("std");
const Atom = @import("Atom.zig");
const File = @import("file.zig").File;
const MachO = @import("../MachO.zig");
const Object = @import("Object.zig");
const Symbol = @This();

File diff suppressed because it is too large Load Diff

View File

@ -1,495 +1,204 @@
//! An algorithm for dead stripping of unreferenced Atoms.
pub fn gcAtoms(macho_file: *MachO) !void { pub fn gcAtoms(macho_file: *MachO) !void {
const comp = macho_file.base.comp; const gpa = macho_file.base.allocator;
const gpa = comp.gpa;
var arena = std.heap.ArenaAllocator.init(gpa); var objects = try std.ArrayList(File.Index).initCapacity(gpa, macho_file.objects.items.len + 1);
defer arena.deinit(); defer objects.deinit();
for (macho_file.objects.items) |index| objects.appendAssumeCapacity(index);
if (macho_file.internal_object_index) |index| objects.appendAssumeCapacity(index);
var roots = AtomTable.init(arena.allocator()); var roots = std.ArrayList(*Atom).init(gpa);
try roots.ensureUnusedCapacity(@as(u32, @intCast(macho_file.globals.items.len))); defer roots.deinit();
var alive = AtomTable.init(arena.allocator()); try collectRoots(&roots, objects.items, macho_file);
try alive.ensureTotalCapacity(@as(u32, @intCast(macho_file.atoms.items.len))); mark(roots.items, objects.items, macho_file);
prune(objects.items, macho_file);
try collectRoots(macho_file, &roots);
mark(macho_file, roots, &alive);
prune(macho_file, alive);
} }
fn addRoot(macho_file: *MachO, roots: *AtomTable, file: u32, sym_loc: SymbolWithLoc) !void { fn collectRoots(roots: *std.ArrayList(*Atom), objects: []const File.Index, macho_file: *MachO) !void {
const sym = macho_file.getSymbol(sym_loc); for (objects) |index| {
assert(!sym.undf()); const object = macho_file.getFile(index).?;
const object = &macho_file.objects.items[file]; for (object.getSymbols()) |sym_index| {
const atom_index = object.getAtomIndexForSymbol(sym_loc.sym_index).?; // panic here means fatal error const sym = macho_file.getSymbol(sym_index);
log.debug("root(ATOM({d}, %{d}, {d}))", .{ const file = sym.getFile(macho_file) orelse continue;
atom_index, if (file.getIndex() != index) continue;
macho_file.getAtom(atom_index).sym_index, if (sym.flags.no_dead_strip or (macho_file.options.dylib and sym.visibility == .global))
file, try markSymbol(sym, roots, macho_file);
}); }
_ = try roots.getOrPut(atom_index);
}
fn collectRoots(macho_file: *MachO, roots: *AtomTable) !void { for (object.getAtoms()) |atom_index| {
log.debug("collecting roots", .{}); const atom = macho_file.getAtom(atom_index).?;
const isec = atom.getInputSection(macho_file);
switch (isec.type()) {
macho.S_MOD_INIT_FUNC_POINTERS,
macho.S_MOD_TERM_FUNC_POINTERS,
=> if (markAtom(atom)) try roots.append(atom),
const comp = macho_file.base.comp; else => if (isec.isDontDeadStrip() and markAtom(atom)) {
try roots.append(atom);
switch (comp.config.output_mode) { },
.Exe => {
// Add entrypoint as GC root
if (macho_file.getEntryPoint()) |global| {
if (global.getFile()) |file| {
try addRoot(macho_file, roots, file, global);
} else {
assert(macho_file.getSymbol(global).undf()); // Stub as our entrypoint is in a dylib.
}
} }
}, }
else => |other| {
assert(other == .Lib);
// Add exports as GC roots
for (macho_file.globals.items) |global| {
const sym = macho_file.getSymbol(global);
if (sym.undf()) continue;
if (sym.n_desc == MachO.N_BOUNDARY) continue;
if (global.getFile()) |file| {
try addRoot(macho_file, roots, file, global);
}
}
},
} }
// Add all symbols force-defined by the user. for (macho_file.objects.items) |index| {
for (comp.force_undefined_symbols.keys()) |sym_name| { for (macho_file.getFile(index).?.object.unwind_records.items) |cu_index| {
const global_index = macho_file.resolver.get(sym_name).?; const cu = macho_file.getUnwindRecord(cu_index);
const global = macho_file.globals.items[global_index]; if (!cu.alive) continue;
const sym = macho_file.getSymbol(global); if (cu.getFde(macho_file)) |fde| {
assert(!sym.undf()); if (fde.getCie(macho_file).getPersonality(macho_file)) |sym| try markSymbol(sym, roots, macho_file);
try addRoot(macho_file, roots, global.getFile().?, global); } else if (cu.getPersonality(macho_file)) |sym| try markSymbol(sym, roots, macho_file);
}
} }
for (macho_file.objects.items) |object| { for (macho_file.undefined_symbols.items) |sym_index| {
const has_subsections = object.header.flags & macho.MH_SUBSECTIONS_VIA_SYMBOLS != 0; const sym = macho_file.getSymbol(sym_index);
try markSymbol(sym, roots, macho_file);
}
for (object.atoms.items) |atom_index| { for (&[_]?Symbol.Index{
const is_gc_root = blk: { macho_file.entry_index,
// Modelled after ld64 which treats each object file compiled without MH_SUBSECTIONS_VIA_SYMBOLS macho_file.dyld_stub_binder_index,
// as a root. macho_file.objc_msg_send_index,
if (!has_subsections) break :blk true; }) |index| {
if (index) |idx| {
const atom = macho_file.getAtom(atom_index); const sym = macho_file.getSymbol(idx);
const sect_id = if (object.getSourceSymbol(atom.sym_index)) |source_sym| try markSymbol(sym, roots, macho_file);
source_sym.n_sect - 1
else sect_id: {
const nbase = @as(u32, @intCast(object.in_symtab.?.len));
const sect_id = @as(u8, @intCast(atom.sym_index - nbase));
break :sect_id sect_id;
};
const source_sect = object.getSourceSection(sect_id);
if (source_sect.isDontDeadStrip()) break :blk true;
switch (source_sect.type()) {
macho.S_MOD_INIT_FUNC_POINTERS,
macho.S_MOD_TERM_FUNC_POINTERS,
=> break :blk true,
else => break :blk false,
}
};
if (is_gc_root) {
_ = try roots.getOrPut(atom_index);
log.debug("root(ATOM({d}, %{d}, {?d}))", .{
atom_index,
macho_file.getAtom(atom_index).sym_index,
macho_file.getAtom(atom_index).getFile(),
});
}
} }
} }
} }
fn markLive(macho_file: *MachO, atom_index: Atom.Index, alive: *AtomTable) void { fn markSymbol(sym: *Symbol, roots: *std.ArrayList(*Atom), macho_file: *MachO) !void {
if (alive.contains(atom_index)) return; const atom = sym.getAtom(macho_file) orelse return;
if (markAtom(atom)) try roots.append(atom);
const atom = macho_file.getAtom(atom_index);
const sym_loc = atom.getSymbolWithLoc();
log.debug("mark(ATOM({d}, %{d}, {?d}))", .{ atom_index, sym_loc.sym_index, sym_loc.getFile() });
alive.putAssumeCapacityNoClobber(atom_index, {});
const target = macho_file.base.comp.root_mod.resolved_target.result;
const cpu_arch = target.cpu.arch;
const sym = macho_file.getSymbol(atom.getSymbolWithLoc());
const header = macho_file.sections.items(.header)[sym.n_sect - 1];
if (header.isZerofill()) return;
const code = Atom.getAtomCode(macho_file, atom_index);
const relocs = Atom.getAtomRelocs(macho_file, atom_index);
const ctx = Atom.getRelocContext(macho_file, atom_index);
for (relocs) |rel| {
const reloc_target = switch (cpu_arch) {
.aarch64 => switch (@as(macho.reloc_type_arm64, @enumFromInt(rel.r_type))) {
.ARM64_RELOC_ADDEND => continue,
else => Atom.parseRelocTarget(macho_file, .{
.object_id = atom.getFile().?,
.rel = rel,
.code = code,
.base_offset = ctx.base_offset,
.base_addr = ctx.base_addr,
}),
},
.x86_64 => Atom.parseRelocTarget(macho_file, .{
.object_id = atom.getFile().?,
.rel = rel,
.code = code,
.base_offset = ctx.base_offset,
.base_addr = ctx.base_addr,
}),
else => unreachable,
};
const target_sym = macho_file.getSymbol(reloc_target);
if (target_sym.undf()) continue;
if (reloc_target.getFile() == null) {
const target_sym_name = macho_file.getSymbolName(reloc_target);
if (mem.eql(u8, "__mh_execute_header", target_sym_name)) continue;
if (mem.eql(u8, "___dso_handle", target_sym_name)) continue;
unreachable; // referenced symbol not found
}
const object = macho_file.objects.items[reloc_target.getFile().?];
const target_atom_index = object.getAtomIndexForSymbol(reloc_target.sym_index).?;
log.debug(" following ATOM({d}, %{d}, {?d})", .{
target_atom_index,
macho_file.getAtom(target_atom_index).sym_index,
macho_file.getAtom(target_atom_index).getFile(),
});
markLive(macho_file, target_atom_index, alive);
}
} }
fn refersLive(macho_file: *MachO, atom_index: Atom.Index, alive: AtomTable) bool { fn markAtom(atom: *Atom) bool {
const atom = macho_file.getAtom(atom_index); const already_visited = atom.flags.visited;
const sym_loc = atom.getSymbolWithLoc(); atom.flags.visited = true;
return atom.flags.alive and !already_visited;
log.debug("refersLive(ATOM({d}, %{d}, {?d}))", .{ atom_index, sym_loc.sym_index, sym_loc.getFile() });
const target = macho_file.base.comp.root_mod.resolved_target.result;
const cpu_arch = target.cpu.arch;
const sym = macho_file.getSymbol(sym_loc);
const header = macho_file.sections.items(.header)[sym.n_sect - 1];
assert(!header.isZerofill());
const code = Atom.getAtomCode(macho_file, atom_index);
const relocs = Atom.getAtomRelocs(macho_file, atom_index);
const ctx = Atom.getRelocContext(macho_file, atom_index);
for (relocs) |rel| {
const reloc_target = switch (cpu_arch) {
.aarch64 => switch (@as(macho.reloc_type_arm64, @enumFromInt(rel.r_type))) {
.ARM64_RELOC_ADDEND => continue,
else => Atom.parseRelocTarget(macho_file, .{
.object_id = atom.getFile().?,
.rel = rel,
.code = code,
.base_offset = ctx.base_offset,
.base_addr = ctx.base_addr,
}),
},
.x86_64 => Atom.parseRelocTarget(macho_file, .{
.object_id = atom.getFile().?,
.rel = rel,
.code = code,
.base_offset = ctx.base_offset,
.base_addr = ctx.base_addr,
}),
else => unreachable,
};
const object = macho_file.objects.items[reloc_target.getFile().?];
const target_atom_index = object.getAtomIndexForSymbol(reloc_target.sym_index) orelse {
log.debug("atom for symbol '{s}' not found; skipping...", .{macho_file.getSymbolName(reloc_target)});
continue;
};
if (alive.contains(target_atom_index)) {
log.debug(" refers live ATOM({d}, %{d}, {?d})", .{
target_atom_index,
macho_file.getAtom(target_atom_index).sym_index,
macho_file.getAtom(target_atom_index).getFile(),
});
return true;
}
}
return false;
} }
fn mark(macho_file: *MachO, roots: AtomTable, alive: *AtomTable) void { fn mark(roots: []*Atom, objects: []const File.Index, macho_file: *MachO) void {
var it = roots.keyIterator(); for (roots) |root| {
while (it.next()) |root| { markLive(root, macho_file);
markLive(macho_file, root.*, alive);
} }
var loop: bool = true; var loop: bool = true;
while (loop) { while (loop) {
loop = false; loop = false;
for (macho_file.objects.items) |object| { for (objects) |index| {
for (object.atoms.items) |atom_index| { for (macho_file.getFile(index).?.getAtoms()) |atom_index| {
if (alive.contains(atom_index)) continue; const atom = macho_file.getAtom(atom_index).?;
const isec = atom.getInputSection(macho_file);
const atom = macho_file.getAtom(atom_index); if (isec.isDontDeadStripIfReferencesLive() and !atom.flags.alive and refersLive(atom, macho_file)) {
const sect_id = if (object.getSourceSymbol(atom.sym_index)) |source_sym| markLive(atom, macho_file);
source_sym.n_sect - 1 loop = true;
else blk: {
const nbase = @as(u32, @intCast(object.in_symtab.?.len));
const sect_id = @as(u8, @intCast(atom.sym_index - nbase));
break :blk sect_id;
};
const source_sect = object.getSourceSection(sect_id);
if (source_sect.isDontDeadStripIfReferencesLive()) {
if (refersLive(macho_file, atom_index, alive.*)) {
markLive(macho_file, atom_index, alive);
loop = true;
}
}
}
}
}
for (macho_file.objects.items, 0..) |_, object_id| {
// Traverse unwind and eh_frame records noting if the source symbol has been marked, and if so,
// marking all references as live.
markUnwindRecords(macho_file, @as(u32, @intCast(object_id)), alive);
}
}
fn markUnwindRecords(macho_file: *MachO, object_id: u32, alive: *AtomTable) void {
const object = &macho_file.objects.items[object_id];
const target = macho_file.base.comp.root_mod.resolved_target.result;
const cpu_arch = target.cpu.arch;
const unwind_records = object.getUnwindRecords();
for (object.exec_atoms.items) |atom_index| {
var inner_syms_it = Atom.getInnerSymbolsIterator(macho_file, atom_index);
if (!object.hasUnwindRecords()) {
if (alive.contains(atom_index)) {
// Mark references live and continue.
markEhFrameRecords(macho_file, object_id, atom_index, alive);
} else {
while (inner_syms_it.next()) |sym| {
if (object.eh_frame_records_lookup.get(sym)) |fde_offset| {
// Mark dead and continue.
object.eh_frame_relocs_lookup.getPtr(fde_offset).?.dead = true;
}
}
}
continue;
}
while (inner_syms_it.next()) |sym| {
const record_id = object.unwind_records_lookup.get(sym) orelse continue;
if (object.unwind_relocs_lookup[record_id].dead) continue; // already marked, nothing to do
if (!alive.contains(atom_index)) {
// Mark the record dead and continue.
object.unwind_relocs_lookup[record_id].dead = true;
if (object.eh_frame_records_lookup.get(sym)) |fde_offset| {
object.eh_frame_relocs_lookup.getPtr(fde_offset).?.dead = true;
}
continue;
}
const record = unwind_records[record_id];
if (UnwindInfo.UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) {
markEhFrameRecords(macho_file, object_id, atom_index, alive);
} else {
if (UnwindInfo.getPersonalityFunctionReloc(macho_file, object_id, record_id)) |rel| {
const reloc_target = Atom.parseRelocTarget(macho_file, .{
.object_id = object_id,
.rel = rel,
.code = mem.asBytes(&record),
.base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry))),
});
const target_sym = macho_file.getSymbol(reloc_target);
if (!target_sym.undf()) {
const target_object = macho_file.objects.items[reloc_target.getFile().?];
const target_atom_index = target_object.getAtomIndexForSymbol(reloc_target.sym_index).?;
markLive(macho_file, target_atom_index, alive);
}
}
if (UnwindInfo.getLsdaReloc(macho_file, object_id, record_id)) |rel| {
const reloc_target = Atom.parseRelocTarget(macho_file, .{
.object_id = object_id,
.rel = rel,
.code = mem.asBytes(&record),
.base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry))),
});
const target_object = macho_file.objects.items[reloc_target.getFile().?];
const target_atom_index = target_object.getAtomIndexForSymbol(reloc_target.sym_index).?;
markLive(macho_file, target_atom_index, alive);
} }
} }
} }
} }
} }
fn markEhFrameRecords(macho_file: *MachO, object_id: u32, atom_index: Atom.Index, alive: *AtomTable) void { fn markLive(atom: *Atom, macho_file: *MachO) void {
const target = macho_file.base.comp.root_mod.resolved_target.result; assert(atom.flags.visited);
const cpu_arch = target.cpu.arch; atom.flags.alive = true;
const object = &macho_file.objects.items[object_id]; track_live_log.debug("{}marking live atom({d},{s})", .{
var it = object.getEhFrameRecordsIterator(); track_live_level,
var inner_syms_it = Atom.getInnerSymbolsIterator(macho_file, atom_index); atom.atom_index,
atom.getName(macho_file),
});
while (inner_syms_it.next()) |sym| { if (build_options.enable_logging)
const fde_offset = object.eh_frame_records_lookup.get(sym) orelse continue; // Continue in case we hit a temp symbol alias track_live_level.incr();
it.seekTo(fde_offset);
const fde = (it.next() catch continue).?; // We don't care about the error at this point since it was already handled
const cie_ptr = fde.getCiePointerSource(object_id, macho_file, fde_offset); for (atom.getRelocs(macho_file)) |rel| {
const cie_offset = fde_offset + 4 - cie_ptr; const target_atom = switch (rel.tag) {
it.seekTo(cie_offset); .local => rel.getTargetAtom(macho_file),
const cie = (it.next() catch continue).?; // We don't care about the error at this point since it was already handled .@"extern" => rel.getTargetSymbol(macho_file).getAtom(macho_file),
};
switch (cpu_arch) { if (target_atom) |ta| {
.aarch64 => { if (markAtom(ta)) markLive(ta, macho_file);
// Mark FDE references which should include any referenced LSDA record
const relocs = eh_frame.getRelocs(macho_file, object_id, fde_offset);
for (relocs) |rel| {
const reloc_target = Atom.parseRelocTarget(macho_file, .{
.object_id = object_id,
.rel = rel,
.code = fde.data,
.base_offset = @as(i32, @intCast(fde_offset)) + 4,
});
const target_sym = macho_file.getSymbol(reloc_target);
if (!target_sym.undf()) blk: {
const target_object = macho_file.objects.items[reloc_target.getFile().?];
const target_atom_index = target_object.getAtomIndexForSymbol(reloc_target.sym_index) orelse
break :blk;
markLive(macho_file, target_atom_index, alive);
}
}
},
.x86_64 => {
const sect = object.getSourceSection(object.eh_frame_sect_id.?);
const lsda_ptr = fde.getLsdaPointer(cie, .{
.base_addr = sect.addr,
.base_offset = fde_offset,
}) catch continue; // We don't care about the error at this point since it was already handled
if (lsda_ptr) |lsda_address| {
// Mark LSDA record as live
const sym_index = object.getSymbolByAddress(lsda_address, null);
const target_atom_index = object.getAtomIndexForSymbol(sym_index).?;
markLive(macho_file, target_atom_index, alive);
}
},
else => unreachable,
} }
}
// Mark CIE references which should include any referenced personalities for (atom.getUnwindRecords(macho_file)) |cu_index| {
// that are defined locally. const cu = macho_file.getUnwindRecord(cu_index);
if (cie.getPersonalityPointerReloc(macho_file, object_id, cie_offset)) |reloc_target| { const cu_atom = cu.getAtom(macho_file);
const target_sym = macho_file.getSymbol(reloc_target); if (markAtom(cu_atom)) markLive(cu_atom, macho_file);
if (!target_sym.undf()) {
const target_object = macho_file.objects.items[reloc_target.getFile().?]; if (cu.getLsdaAtom(macho_file)) |lsda| {
const target_atom_index = target_object.getAtomIndexForSymbol(reloc_target.sym_index).?; if (markAtom(lsda)) markLive(lsda, macho_file);
markLive(macho_file, target_atom_index, alive); }
if (cu.getFde(macho_file)) |fde| {
const fde_atom = fde.getAtom(macho_file);
if (markAtom(fde_atom)) markLive(fde_atom, macho_file);
if (fde.getLsdaAtom(macho_file)) |lsda| {
if (markAtom(lsda)) markLive(lsda, macho_file);
} }
} }
} }
} }
fn prune(macho_file: *MachO, alive: AtomTable) void { fn refersLive(atom: *Atom, macho_file: *MachO) bool {
log.debug("pruning dead atoms", .{}); for (atom.getRelocs(macho_file)) |rel| {
for (macho_file.objects.items) |*object| { const target_atom = switch (rel.tag) {
var i: usize = 0; .local => rel.getTargetAtom(macho_file),
while (i < object.atoms.items.len) { .@"extern" => rel.getTargetSymbol(macho_file).getAtom(macho_file),
const atom_index = object.atoms.items[i]; };
if (alive.contains(atom_index)) { if (target_atom) |ta| {
i += 1; if (ta.flags.alive) return true;
continue; }
} }
return false;
}
const atom = macho_file.getAtom(atom_index); fn prune(objects: []const File.Index, macho_file: *MachO) void {
const sym_loc = atom.getSymbolWithLoc(); for (objects) |index| {
for (macho_file.getFile(index).?.getAtoms()) |atom_index| {
log.debug("prune(ATOM({d}, %{d}, {?d}))", .{ const atom = macho_file.getAtom(atom_index).?;
atom_index, if (atom.flags.alive and !atom.flags.visited) {
sym_loc.sym_index, atom.flags.alive = false;
sym_loc.getFile(), atom.markUnwindRecordsDead(macho_file);
});
log.debug(" {s} in {s}", .{ macho_file.getSymbolName(sym_loc), object.name });
const sym = macho_file.getSymbolPtr(sym_loc);
const sect_id = sym.n_sect - 1;
var section = macho_file.sections.get(sect_id);
section.header.size -= atom.size;
if (atom.prev_index) |prev_index| {
const prev = macho_file.getAtomPtr(prev_index);
prev.next_index = atom.next_index;
} else {
if (atom.next_index) |next_index| {
section.first_atom_index = next_index;
}
}
if (atom.next_index) |next_index| {
const next = macho_file.getAtomPtr(next_index);
next.prev_index = atom.prev_index;
} else {
if (atom.prev_index) |prev_index| {
section.last_atom_index = prev_index;
} else {
assert(section.header.size == 0);
section.first_atom_index = null;
section.last_atom_index = null;
}
}
macho_file.sections.set(sect_id, section);
_ = object.atoms.swapRemove(i);
sym.n_desc = MachO.N_DEAD;
var inner_sym_it = Atom.getInnerSymbolsIterator(macho_file, atom_index);
while (inner_sym_it.next()) |inner| {
const inner_sym = macho_file.getSymbolPtr(inner);
inner_sym.n_desc = MachO.N_DEAD;
}
if (Atom.getSectionAlias(macho_file, atom_index)) |alias| {
const alias_sym = macho_file.getSymbolPtr(alias);
alias_sym.n_desc = MachO.N_DEAD;
} }
} }
} }
} }
const std = @import("std"); const Level = struct {
value: usize = 0,
fn incr(self: *@This()) void {
self.value += 1;
}
pub fn format(
self: *const @This(),
comptime unused_fmt_string: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = unused_fmt_string;
_ = options;
try writer.writeByteNTimes(' ', self.value);
}
};
var track_live_level: Level = .{};
const assert = std.debug.assert; const assert = std.debug.assert;
const eh_frame = @import("eh_frame.zig"); const build_options = @import("build_options");
const log = std.log.scoped(.dead_strip); const log = std.log.scoped(.dead_strip);
const macho = std.macho; const macho = std.macho;
const math = std.math; const math = std.math;
const mem = std.mem; const mem = std.mem;
const trace = @import("../tracy.zig").trace;
const track_live_log = std.log.scoped(.dead_strip_track_live);
const std = @import("std");
const Allocator = mem.Allocator; const Allocator = mem.Allocator;
const Atom = @import("Atom.zig"); const Atom = @import("Atom.zig");
const File = @import("file.zig").File;
const MachO = @import("../MachO.zig"); const MachO = @import("../MachO.zig");
const SymbolWithLoc = MachO.SymbolWithLoc; const Symbol = @import("Symbol.zig");
const UnwindInfo = @import("UnwindInfo.zig");
const AtomTable = std.AutoHashMap(Atom.Index, void);

View File

@ -1,3 +1,14 @@
const Rebase = @This();
const std = @import("std");
const assert = std.debug.assert;
const leb = std.leb;
const log = std.log.scoped(.dyld_info);
const macho = std.macho;
const testing = std.testing;
const Allocator = std.mem.Allocator;
entries: std.ArrayListUnmanaged(Entry) = .{}, entries: std.ArrayListUnmanaged(Entry) = .{},
buffer: std.ArrayListUnmanaged(u8) = .{}, buffer: std.ArrayListUnmanaged(u8) = .{},
@ -168,7 +179,7 @@ fn rebaseTimesSkip(count: usize, skip: u64, writer: anytype) !void {
fn addAddr(addr: u64, writer: anytype) !void { fn addAddr(addr: u64, writer: anytype) !void {
log.debug(">>> add: {x}", .{addr}); log.debug(">>> add: {x}", .{addr});
if (std.mem.isAlignedGeneric(u64, addr, @sizeOf(u64))) { if (std.mem.isAligned(addr, @sizeOf(u64))) {
const imm = @divExact(addr, @sizeOf(u64)); const imm = @divExact(addr, @sizeOf(u64));
if (imm <= 0xf) { if (imm <= 0xf) {
try writer.writeByte(macho.REBASE_OPCODE_ADD_ADDR_IMM_SCALED | @as(u4, @truncate(imm))); try writer.writeByte(macho.REBASE_OPCODE_ADD_ADDR_IMM_SCALED | @as(u4, @truncate(imm)));
@ -561,14 +572,3 @@ test "rebase - composite" {
macho.REBASE_OPCODE_DONE, macho.REBASE_OPCODE_DONE,
}, rebase.buffer.items); }, rebase.buffer.items);
} }
const Rebase = @This();
const std = @import("std");
const assert = std.debug.assert;
const leb = std.leb;
const log = std.log.scoped(.dyld_info);
const macho = std.macho;
const testing = std.testing;
const Allocator = std.mem.Allocator;

View File

@ -28,347 +28,16 @@
//! After the optional exported symbol information is a byte of how many edges (0-255) that //! After the optional exported symbol information is a byte of how many edges (0-255) that
//! this node has leaving it, followed by each edge. Each edge is a zero terminated UTF8 of //! this node has leaving it, followed by each edge. Each edge is a zero terminated UTF8 of
//! the addition chars in the symbol, followed by a uleb128 offset for the node that edge points to. //! the addition chars in the symbol, followed by a uleb128 offset for the node that edge points to.
/// The root node of the trie. const Trie = @This();
root: ?*Node = null,
/// If you want to access nodes ordered in DFS fashion, const std = @import("std");
/// you should call `finalize` first since the nodes const mem = std.mem;
/// in this container are not guaranteed to not be stale const leb = std.leb;
/// if more insertions took place after the last `finalize` const log = std.log.scoped(.macho);
/// call. const macho = std.macho;
ordered_nodes: std.ArrayListUnmanaged(*Node) = .{}, const testing = std.testing;
const assert = std.debug.assert;
/// The size of the trie in bytes. const Allocator = mem.Allocator;
/// This value may be outdated if there were additional
/// insertions performed after `finalize` was called.
/// Call `finalize` before accessing this value to ensure
/// it is up-to-date.
size: u64 = 0,
/// Number of nodes currently in the trie.
node_count: usize = 0,
trie_dirty: bool = true,
/// Export symbol that is to be placed in the trie.
pub const ExportSymbol = struct {
/// Name of the symbol.
name: []const u8,
/// Offset of this symbol's virtual memory address from the beginning
/// of the __TEXT segment.
vmaddr_offset: u64,
/// Export flags of this exported symbol.
export_flags: u64,
};
/// Insert a symbol into the trie, updating the prefixes in the process.
/// This operation may change the layout of the trie by splicing edges in
/// certain circumstances.
pub fn put(self: *Trie, allocator: Allocator, symbol: ExportSymbol) !void {
const node = try self.root.?.put(allocator, symbol.name);
node.terminal_info = .{
.vmaddr_offset = symbol.vmaddr_offset,
.export_flags = symbol.export_flags,
};
self.trie_dirty = true;
}
/// Finalizes this trie for writing to a byte stream.
/// This step performs multiple passes through the trie ensuring
/// there are no gaps after every `Node` is ULEB128 encoded.
/// Call this method before trying to `write` the trie to a byte stream.
pub fn finalize(self: *Trie, allocator: Allocator) !void {
if (!self.trie_dirty) return;
self.ordered_nodes.shrinkRetainingCapacity(0);
try self.ordered_nodes.ensureTotalCapacity(allocator, self.node_count);
var fifo = std.fifo.LinearFifo(*Node, .Dynamic).init(allocator);
defer fifo.deinit();
try fifo.writeItem(self.root.?);
while (fifo.readItem()) |next| {
for (next.edges.items) |*edge| {
try fifo.writeItem(edge.to);
}
self.ordered_nodes.appendAssumeCapacity(next);
}
var more: bool = true;
while (more) {
self.size = 0;
more = false;
for (self.ordered_nodes.items) |node| {
const res = try node.finalize(self.size);
self.size += res.node_size;
if (res.updated) more = true;
}
}
self.trie_dirty = false;
}
const ReadError = error{
OutOfMemory,
EndOfStream,
Overflow,
};
/// Parse the trie from a byte stream.
pub fn read(self: *Trie, allocator: Allocator, reader: anytype) ReadError!usize {
return self.root.?.read(allocator, reader);
}
/// Write the trie to a byte stream.
/// Panics if the trie was not finalized using `finalize` before calling this method.
pub fn write(self: Trie, writer: anytype) !u64 {
assert(!self.trie_dirty);
var counting_writer = std.io.countingWriter(writer);
for (self.ordered_nodes.items) |node| {
try node.write(counting_writer.writer());
}
return counting_writer.bytes_written;
}
pub fn init(self: *Trie, allocator: Allocator) !void {
assert(self.root == null);
const root = try allocator.create(Node);
root.* = .{ .base = self };
self.root = root;
self.node_count += 1;
}
pub fn deinit(self: *Trie, allocator: Allocator) void {
if (self.root) |root| {
root.deinit(allocator);
allocator.destroy(root);
}
self.ordered_nodes.deinit(allocator);
}
test "Trie node count" {
const gpa = testing.allocator;
var trie: Trie = .{};
defer trie.deinit(gpa);
try trie.init(gpa);
try testing.expectEqual(trie.node_count, 0);
try testing.expect(trie.root == null);
try trie.put(gpa, .{
.name = "_main",
.vmaddr_offset = 0,
.export_flags = 0,
});
try testing.expectEqual(trie.node_count, 2);
// Inserting the same node shouldn't update the trie.
try trie.put(gpa, .{
.name = "_main",
.vmaddr_offset = 0,
.export_flags = 0,
});
try testing.expectEqual(trie.node_count, 2);
try trie.put(gpa, .{
.name = "__mh_execute_header",
.vmaddr_offset = 0x1000,
.export_flags = 0,
});
try testing.expectEqual(trie.node_count, 4);
// Inserting the same node shouldn't update the trie.
try trie.put(gpa, .{
.name = "__mh_execute_header",
.vmaddr_offset = 0x1000,
.export_flags = 0,
});
try testing.expectEqual(trie.node_count, 4);
try trie.put(gpa, .{
.name = "_main",
.vmaddr_offset = 0,
.export_flags = 0,
});
try testing.expectEqual(trie.node_count, 4);
}
test "Trie basic" {
const gpa = testing.allocator;
var trie: Trie = .{};
defer trie.deinit(gpa);
try trie.init(gpa);
// root --- _st ---> node
try trie.put(gpa, .{
.name = "_st",
.vmaddr_offset = 0,
.export_flags = 0,
});
try testing.expect(trie.root.?.edges.items.len == 1);
try testing.expect(mem.eql(u8, trie.root.?.edges.items[0].label, "_st"));
{
// root --- _st ---> node --- art ---> node
try trie.put(gpa, .{
.name = "_start",
.vmaddr_offset = 0,
.export_flags = 0,
});
try testing.expect(trie.root.?.edges.items.len == 1);
const nextEdge = &trie.root.?.edges.items[0];
try testing.expect(mem.eql(u8, nextEdge.label, "_st"));
try testing.expect(nextEdge.to.edges.items.len == 1);
try testing.expect(mem.eql(u8, nextEdge.to.edges.items[0].label, "art"));
}
{
// root --- _ ---> node --- st ---> node --- art ---> node
// |
// | --- main ---> node
try trie.put(gpa, .{
.name = "_main",
.vmaddr_offset = 0,
.export_flags = 0,
});
try testing.expect(trie.root.?.edges.items.len == 1);
const nextEdge = &trie.root.?.edges.items[0];
try testing.expect(mem.eql(u8, nextEdge.label, "_"));
try testing.expect(nextEdge.to.edges.items.len == 2);
try testing.expect(mem.eql(u8, nextEdge.to.edges.items[0].label, "st"));
try testing.expect(mem.eql(u8, nextEdge.to.edges.items[1].label, "main"));
const nextNextEdge = &nextEdge.to.edges.items[0];
try testing.expect(mem.eql(u8, nextNextEdge.to.edges.items[0].label, "art"));
}
}
fn expectEqualHexStrings(expected: []const u8, given: []const u8) !void {
assert(expected.len > 0);
if (mem.eql(u8, expected, given)) return;
const expected_fmt = try std.fmt.allocPrint(testing.allocator, "{x}", .{std.fmt.fmtSliceHexLower(expected)});
defer testing.allocator.free(expected_fmt);
const given_fmt = try std.fmt.allocPrint(testing.allocator, "{x}", .{std.fmt.fmtSliceHexLower(given)});
defer testing.allocator.free(given_fmt);
const idx = mem.indexOfDiff(u8, expected_fmt, given_fmt).?;
const padding = try testing.allocator.alloc(u8, idx + 5);
defer testing.allocator.free(padding);
@memset(padding, ' ');
std.debug.print("\nEXP: {s}\nGIV: {s}\n{s}^ -- first differing byte\n", .{ expected_fmt, given_fmt, padding });
return error.TestFailed;
}
test "write Trie to a byte stream" {
var gpa = testing.allocator;
var trie: Trie = .{};
defer trie.deinit(gpa);
try trie.init(gpa);
try trie.put(gpa, .{
.name = "__mh_execute_header",
.vmaddr_offset = 0,
.export_flags = 0,
});
try trie.put(gpa, .{
.name = "_main",
.vmaddr_offset = 0x1000,
.export_flags = 0,
});
try trie.finalize(gpa);
try trie.finalize(gpa); // Finalizing multiple times is a nop subsequently unless we add new nodes.
const exp_buffer = [_]u8{
0x0, 0x1, // node root
0x5f, 0x0, 0x5, // edge '_'
0x0, 0x2, // non-terminal node
0x5f, 0x6d, 0x68, 0x5f, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, // edge '_mh_execute_header'
0x65, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x0, 0x21, // edge '_mh_execute_header'
0x6d, 0x61, 0x69, 0x6e, 0x0, 0x25, // edge 'main'
0x2, 0x0, 0x0, 0x0, // terminal node
0x3, 0x0, 0x80, 0x20, 0x0, // terminal node
};
const buffer = try gpa.alloc(u8, trie.size);
defer gpa.free(buffer);
var stream = std.io.fixedBufferStream(buffer);
{
_ = try trie.write(stream.writer());
try expectEqualHexStrings(&exp_buffer, buffer);
}
{
// Writing finalized trie again should yield the same result.
try stream.seekTo(0);
_ = try trie.write(stream.writer());
try expectEqualHexStrings(&exp_buffer, buffer);
}
}
test "parse Trie from byte stream" {
var gpa = testing.allocator;
const in_buffer = [_]u8{
0x0, 0x1, // node root
0x5f, 0x0, 0x5, // edge '_'
0x0, 0x2, // non-terminal node
0x5f, 0x6d, 0x68, 0x5f, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, // edge '_mh_execute_header'
0x65, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x0, 0x21, // edge '_mh_execute_header'
0x6d, 0x61, 0x69, 0x6e, 0x0, 0x25, // edge 'main'
0x2, 0x0, 0x0, 0x0, // terminal node
0x3, 0x0, 0x80, 0x20, 0x0, // terminal node
};
var in_stream = std.io.fixedBufferStream(&in_buffer);
var trie: Trie = .{};
defer trie.deinit(gpa);
try trie.init(gpa);
const nread = try trie.read(gpa, in_stream.reader());
try testing.expect(nread == in_buffer.len);
try trie.finalize(gpa);
const out_buffer = try gpa.alloc(u8, trie.size);
defer gpa.free(out_buffer);
var out_stream = std.io.fixedBufferStream(out_buffer);
_ = try trie.write(out_stream.writer());
try expectEqualHexStrings(&in_buffer, out_buffer);
}
test "ordering bug" {
var gpa = testing.allocator;
var trie: Trie = .{};
defer trie.deinit(gpa);
try trie.init(gpa);
try trie.put(gpa, .{
.name = "_asStr",
.vmaddr_offset = 0x558,
.export_flags = 0,
});
try trie.put(gpa, .{
.name = "_a",
.vmaddr_offset = 0x8008,
.export_flags = 0,
});
try trie.finalize(gpa);
const exp_buffer = [_]u8{
0x00, 0x01, 0x5F, 0x61, 0x00, 0x06, 0x04, 0x00,
0x88, 0x80, 0x02, 0x01, 0x73, 0x53, 0x74, 0x72,
0x00, 0x12, 0x03, 0x00, 0xD8, 0x0A, 0x00,
};
const buffer = try gpa.alloc(u8, trie.size);
defer gpa.free(buffer);
var stream = std.io.fixedBufferStream(buffer);
// Writing finalized trie again should yield the same result.
_ = try trie.write(stream.writer());
try expectEqualHexStrings(&exp_buffer, buffer);
}
pub const Node = struct { pub const Node = struct {
base: *Trie, base: *Trie,
@ -601,13 +270,343 @@ pub const Node = struct {
} }
}; };
const Trie = @This(); /// The root node of the trie.
root: ?*Node = null,
const std = @import("std"); /// If you want to access nodes ordered in DFS fashion,
const mem = std.mem; /// you should call `finalize` first since the nodes
const leb = std.leb; /// in this container are not guaranteed to not be stale
const log = std.log.scoped(.link); /// if more insertions took place after the last `finalize`
const macho = std.macho; /// call.
const testing = std.testing; ordered_nodes: std.ArrayListUnmanaged(*Node) = .{},
const assert = std.debug.assert;
const Allocator = mem.Allocator; /// The size of the trie in bytes.
/// This value may be outdated if there were additional
/// insertions performed after `finalize` was called.
/// Call `finalize` before accessing this value to ensure
/// it is up-to-date.
size: u64 = 0,
/// Number of nodes currently in the trie.
node_count: usize = 0,
trie_dirty: bool = true,
/// Export symbol that is to be placed in the trie.
pub const ExportSymbol = struct {
/// Name of the symbol.
name: []const u8,
/// Offset of this symbol's virtual memory address from the beginning
/// of the __TEXT segment.
vmaddr_offset: u64,
/// Export flags of this exported symbol.
export_flags: u64,
};
/// Insert a symbol into the trie, updating the prefixes in the process.
/// This operation may change the layout of the trie by splicing edges in
/// certain circumstances.
pub fn put(self: *Trie, allocator: Allocator, symbol: ExportSymbol) !void {
const node = try self.root.?.put(allocator, symbol.name);
node.terminal_info = .{
.vmaddr_offset = symbol.vmaddr_offset,
.export_flags = symbol.export_flags,
};
self.trie_dirty = true;
}
/// Finalizes this trie for writing to a byte stream.
/// This step performs multiple passes through the trie ensuring
/// there are no gaps after every `Node` is ULEB128 encoded.
/// Call this method before trying to `write` the trie to a byte stream.
pub fn finalize(self: *Trie, allocator: Allocator) !void {
if (!self.trie_dirty) return;
self.ordered_nodes.shrinkRetainingCapacity(0);
try self.ordered_nodes.ensureTotalCapacity(allocator, self.node_count);
var fifo = std.fifo.LinearFifo(*Node, .Dynamic).init(allocator);
defer fifo.deinit();
try fifo.writeItem(self.root.?);
while (fifo.readItem()) |next| {
for (next.edges.items) |*edge| {
try fifo.writeItem(edge.to);
}
self.ordered_nodes.appendAssumeCapacity(next);
}
var more: bool = true;
while (more) {
self.size = 0;
more = false;
for (self.ordered_nodes.items) |node| {
const res = try node.finalize(self.size);
self.size += res.node_size;
if (res.updated) more = true;
}
}
self.trie_dirty = false;
}
const ReadError = error{
OutOfMemory,
EndOfStream,
Overflow,
};
/// Parse the trie from a byte stream.
pub fn read(self: *Trie, allocator: Allocator, reader: anytype) ReadError!usize {
return self.root.?.read(allocator, reader);
}
/// Write the trie to a byte stream.
/// Panics if the trie was not finalized using `finalize` before calling this method.
pub fn write(self: Trie, writer: anytype) !void {
assert(!self.trie_dirty);
for (self.ordered_nodes.items) |node| {
try node.write(writer);
}
}
pub fn init(self: *Trie, allocator: Allocator) !void {
assert(self.root == null);
const root = try allocator.create(Node);
root.* = .{ .base = self };
self.root = root;
self.node_count += 1;
}
pub fn deinit(self: *Trie, allocator: Allocator) void {
if (self.root) |root| {
root.deinit(allocator);
allocator.destroy(root);
}
self.ordered_nodes.deinit(allocator);
}
test "Trie node count" {
const gpa = testing.allocator;
var trie: Trie = .{};
defer trie.deinit(gpa);
try trie.init(gpa);
try testing.expectEqual(@as(usize, 1), trie.node_count);
try testing.expect(trie.root != null);
try trie.put(gpa, .{
.name = "_main",
.vmaddr_offset = 0,
.export_flags = 0,
});
try testing.expectEqual(@as(usize, 2), trie.node_count);
// Inserting the same node shouldn't update the trie.
try trie.put(gpa, .{
.name = "_main",
.vmaddr_offset = 0,
.export_flags = 0,
});
try testing.expectEqual(@as(usize, 2), trie.node_count);
try trie.put(gpa, .{
.name = "__mh_execute_header",
.vmaddr_offset = 0x1000,
.export_flags = 0,
});
try testing.expectEqual(@as(usize, 4), trie.node_count);
// Inserting the same node shouldn't update the trie.
try trie.put(gpa, .{
.name = "__mh_execute_header",
.vmaddr_offset = 0x1000,
.export_flags = 0,
});
try testing.expectEqual(@as(usize, 4), trie.node_count);
try trie.put(gpa, .{
.name = "_main",
.vmaddr_offset = 0,
.export_flags = 0,
});
try testing.expectEqual(@as(usize, 4), trie.node_count);
}
test "Trie basic" {
const gpa = testing.allocator;
var trie: Trie = .{};
defer trie.deinit(gpa);
try trie.init(gpa);
// root --- _st ---> node
try trie.put(gpa, .{
.name = "_st",
.vmaddr_offset = 0,
.export_flags = 0,
});
try testing.expect(trie.root.?.edges.items.len == 1);
try testing.expect(mem.eql(u8, trie.root.?.edges.items[0].label, "_st"));
{
// root --- _st ---> node --- art ---> node
try trie.put(gpa, .{
.name = "_start",
.vmaddr_offset = 0,
.export_flags = 0,
});
try testing.expect(trie.root.?.edges.items.len == 1);
const nextEdge = &trie.root.?.edges.items[0];
try testing.expect(mem.eql(u8, nextEdge.label, "_st"));
try testing.expect(nextEdge.to.edges.items.len == 1);
try testing.expect(mem.eql(u8, nextEdge.to.edges.items[0].label, "art"));
}
{
// root --- _ ---> node --- st ---> node --- art ---> node
// |
// | --- main ---> node
try trie.put(gpa, .{
.name = "_main",
.vmaddr_offset = 0,
.export_flags = 0,
});
try testing.expect(trie.root.?.edges.items.len == 1);
const nextEdge = &trie.root.?.edges.items[0];
try testing.expect(mem.eql(u8, nextEdge.label, "_"));
try testing.expect(nextEdge.to.edges.items.len == 2);
try testing.expect(mem.eql(u8, nextEdge.to.edges.items[0].label, "st"));
try testing.expect(mem.eql(u8, nextEdge.to.edges.items[1].label, "main"));
const nextNextEdge = &nextEdge.to.edges.items[0];
try testing.expect(mem.eql(u8, nextNextEdge.to.edges.items[0].label, "art"));
}
}
fn expectEqualHexStrings(expected: []const u8, given: []const u8) !void {
assert(expected.len > 0);
if (mem.eql(u8, expected, given)) return;
const expected_fmt = try std.fmt.allocPrint(testing.allocator, "{x}", .{std.fmt.fmtSliceHexLower(expected)});
defer testing.allocator.free(expected_fmt);
const given_fmt = try std.fmt.allocPrint(testing.allocator, "{x}", .{std.fmt.fmtSliceHexLower(given)});
defer testing.allocator.free(given_fmt);
const idx = mem.indexOfDiff(u8, expected_fmt, given_fmt).?;
const padding = try testing.allocator.alloc(u8, idx + 5);
defer testing.allocator.free(padding);
@memset(padding, ' ');
std.debug.print("\nEXP: {s}\nGIV: {s}\n{s}^ -- first differing byte\n", .{ expected_fmt, given_fmt, padding });
return error.TestFailed;
}
test "write Trie to a byte stream" {
var gpa = testing.allocator;
var trie: Trie = .{};
defer trie.deinit(gpa);
try trie.init(gpa);
try trie.put(gpa, .{
.name = "__mh_execute_header",
.vmaddr_offset = 0,
.export_flags = 0,
});
try trie.put(gpa, .{
.name = "_main",
.vmaddr_offset = 0x1000,
.export_flags = 0,
});
try trie.finalize(gpa);
try trie.finalize(gpa); // Finalizing mulitple times is a nop subsequently unless we add new nodes.
const exp_buffer = [_]u8{
0x0, 0x1, // node root
0x5f, 0x0, 0x5, // edge '_'
0x0, 0x2, // non-terminal node
0x5f, 0x6d, 0x68, 0x5f, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, // edge '_mh_execute_header'
0x65, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x0, 0x21, // edge '_mh_execute_header'
0x6d, 0x61, 0x69, 0x6e, 0x0, 0x25, // edge 'main'
0x2, 0x0, 0x0, 0x0, // terminal node
0x3, 0x0, 0x80, 0x20, 0x0, // terminal node
};
const buffer = try gpa.alloc(u8, trie.size);
defer gpa.free(buffer);
var stream = std.io.fixedBufferStream(buffer);
{
_ = try trie.write(stream.writer());
try expectEqualHexStrings(&exp_buffer, buffer);
}
{
// Writing finalized trie again should yield the same result.
try stream.seekTo(0);
_ = try trie.write(stream.writer());
try expectEqualHexStrings(&exp_buffer, buffer);
}
}
test "parse Trie from byte stream" {
const gpa = testing.allocator;
const in_buffer = [_]u8{
0x0, 0x1, // node root
0x5f, 0x0, 0x5, // edge '_'
0x0, 0x2, // non-terminal node
0x5f, 0x6d, 0x68, 0x5f, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, // edge '_mh_execute_header'
0x65, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x0, 0x21, // edge '_mh_execute_header'
0x6d, 0x61, 0x69, 0x6e, 0x0, 0x25, // edge 'main'
0x2, 0x0, 0x0, 0x0, // terminal node
0x3, 0x0, 0x80, 0x20, 0x0, // terminal node
};
var in_stream = std.io.fixedBufferStream(&in_buffer);
var trie: Trie = .{};
defer trie.deinit(gpa);
try trie.init(gpa);
const nread = try trie.read(gpa, in_stream.reader());
try testing.expect(nread == in_buffer.len);
try trie.finalize(gpa);
const out_buffer = try gpa.alloc(u8, trie.size);
defer gpa.free(out_buffer);
var out_stream = std.io.fixedBufferStream(out_buffer);
_ = try trie.write(out_stream.writer());
try expectEqualHexStrings(&in_buffer, out_buffer);
}
test "ordering bug" {
const gpa = testing.allocator;
var trie: Trie = .{};
defer trie.deinit(gpa);
try trie.init(gpa);
try trie.put(gpa, .{
.name = "_asStr",
.vmaddr_offset = 0x558,
.export_flags = 0,
});
try trie.put(gpa, .{
.name = "_a",
.vmaddr_offset = 0x8008,
.export_flags = 0,
});
try trie.finalize(gpa);
const exp_buffer = [_]u8{
0x00, 0x01, 0x5F, 0x61, 0x00, 0x06, 0x04, 0x00,
0x88, 0x80, 0x02, 0x01, 0x73, 0x53, 0x74, 0x72,
0x00, 0x12, 0x03, 0x00, 0xD8, 0x0A, 0x00,
};
const buffer = try gpa.alloc(u8, trie.size);
defer gpa.free(buffer);
var stream = std.io.fixedBufferStream(buffer);
// Writing finalized trie again should yield the same result.
_ = try trie.write(stream.writer());
try expectEqualHexStrings(&exp_buffer, buffer);
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,13 @@
const std = @import("std");
const assert = std.debug.assert;
const builtin = @import("builtin");
const log = std.log.scoped(.macho);
const macho = std.macho;
const mem = std.mem;
const native_endian = builtin.target.cpu.arch.endian();
const MachO = @import("../MachO.zig");
pub fn isFatLibrary(file: std.fs.File) bool { pub fn isFatLibrary(file: std.fs.File) bool {
const reader = file.reader(); const reader = file.reader();
const hdr = reader.readStructEndian(macho.fat_header, .big) catch return false; const hdr = reader.readStructEndian(macho.fat_header, .big) catch return false;
@ -7,18 +17,16 @@ pub fn isFatLibrary(file: std.fs.File) bool {
pub const Arch = struct { pub const Arch = struct {
tag: std.Target.Cpu.Arch, tag: std.Target.Cpu.Arch,
offset: u64, offset: u32,
size: u32,
}; };
/// Caller owns the memory. pub fn parseArchs(file: std.fs.File, buffer: *[2]Arch) ![]const Arch {
pub fn parseArchs(gpa: Allocator, file: std.fs.File) ![]const Arch {
const reader = file.reader(); const reader = file.reader();
const fat_header = try reader.readStructEndian(macho.fat_header, .big); const fat_header = try reader.readStructEndian(macho.fat_header, .big);
assert(fat_header.magic == macho.FAT_MAGIC); assert(fat_header.magic == macho.FAT_MAGIC);
var archs = try std.ArrayList(Arch).initCapacity(gpa, fat_header.nfat_arch); var count: usize = 0;
defer archs.deinit();
var fat_arch_index: u32 = 0; var fat_arch_index: u32 = 0;
while (fat_arch_index < fat_header.nfat_arch) : (fat_arch_index += 1) { while (fat_arch_index < fat_header.nfat_arch) : (fat_arch_index += 1) {
const fat_arch = try reader.readStructEndian(macho.fat_arch, .big); const fat_arch = try reader.readStructEndian(macho.fat_arch, .big);
@ -29,16 +37,9 @@ pub fn parseArchs(gpa: Allocator, file: std.fs.File) ![]const Arch {
macho.CPU_TYPE_X86_64 => if (fat_arch.cpusubtype == macho.CPU_SUBTYPE_X86_64_ALL) .x86_64 else continue, macho.CPU_TYPE_X86_64 => if (fat_arch.cpusubtype == macho.CPU_SUBTYPE_X86_64_ALL) .x86_64 else continue,
else => continue, else => continue,
}; };
buffer[count] = .{ .tag = arch, .offset = fat_arch.offset, .size = fat_arch.size };
archs.appendAssumeCapacity(.{ .tag = arch, .offset = fat_arch.offset }); count += 1;
} }
return archs.toOwnedSlice(); return buffer[0..count];
} }
const std = @import("std");
const assert = std.debug.assert;
const log = std.log.scoped(.archive);
const macho = std.macho;
const mem = std.mem;
const Allocator = mem.Allocator;

116
src/link/MachO/file.zig Normal file
View File

@ -0,0 +1,116 @@
pub const File = union(enum) {
internal: *InternalObject,
object: *Object,
dylib: *Dylib,
pub fn getIndex(file: File) Index {
return switch (file) {
inline else => |x| x.index,
};
}
pub fn fmtPath(file: File) std.fmt.Formatter(formatPath) {
return .{ .data = file };
}
fn formatPath(
file: File,
comptime unused_fmt_string: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = unused_fmt_string;
_ = options;
switch (file) {
.internal => try writer.writeAll(""),
.object => |x| try writer.print("{}", .{x.fmtPath()}),
.dylib => |x| try writer.writeAll(x.path),
}
}
pub fn resolveSymbols(file: File, macho_file: *MachO) void {
switch (file) {
.internal => unreachable,
inline else => |x| x.resolveSymbols(macho_file),
}
}
pub fn resetGlobals(file: File, macho_file: *MachO) void {
switch (file) {
.internal => unreachable,
inline else => |x| x.resetGlobals(macho_file),
}
}
/// Encodes symbol rank so that the following ordering applies:
/// * strong in object
/// * weak in object
/// * tentative in object
/// * strong in archive/dylib
/// * weak in archive/dylib
/// * tentative in archive
/// * unclaimed
pub fn getSymbolRank(file: File, args: struct {
archive: bool = false,
weak: bool = false,
tentative: bool = false,
}) u32 {
if (file == .object and !args.archive) {
const base: u32 = blk: {
if (args.tentative) break :blk 3;
break :blk if (args.weak) 2 else 1;
};
return (base << 16) + file.getIndex();
}
const base: u32 = blk: {
if (args.tentative) break :blk 3;
break :blk if (args.weak) 2 else 1;
};
return base + (file.getIndex() << 24);
}
pub fn getSymbols(file: File) []const Symbol.Index {
return switch (file) {
inline else => |x| x.symbols.items,
};
}
pub fn getAtoms(file: File) []const Atom.Index {
return switch (file) {
.dylib => unreachable,
inline else => |x| x.atoms.items,
};
}
pub fn calcSymtabSize(file: File, macho_file: *MachO) !void {
return switch (file) {
inline else => |x| x.calcSymtabSize(macho_file),
};
}
pub fn writeSymtab(file: File, macho_file: *MachO) void {
return switch (file) {
inline else => |x| x.writeSymtab(macho_file),
};
}
pub const Index = u32;
pub const Entry = union(enum) {
null: void,
internal: InternalObject,
object: Object,
dylib: Dylib,
};
};
const macho = std.macho;
const std = @import("std");
const Allocator = std.mem.Allocator;
const Atom = @import("Atom.zig");
const InternalObject = @import("InternalObject.zig");
const MachO = @import("../MachO.zig");
const Object = @import("Object.zig");
const Dylib = @import("Dylib.zig");
const Symbol = @import("Symbol.zig");

View File

@ -9,15 +9,14 @@ pub fn ParallelHasher(comptime Hasher: type) type {
chunk_size: u64 = 0x4000, chunk_size: u64 = 0x4000,
max_file_size: ?u64 = null, max_file_size: ?u64 = null,
}) !void { }) !void {
const tracy = trace(@src());
defer tracy.end();
var wg: WaitGroup = .{}; var wg: WaitGroup = .{};
const file_size = blk: { const file_size = opts.max_file_size orelse try file.getEndPos();
const file_size = opts.max_file_size orelse try file.getEndPos();
break :blk std.math.cast(usize, file_size) orelse return error.Overflow;
};
const chunk_size = std.math.cast(usize, opts.chunk_size) orelse return error.Overflow;
const buffer = try self.allocator.alloc(u8, chunk_size * out.len); const buffer = try self.allocator.alloc(u8, opts.chunk_size * out.len);
defer self.allocator.free(buffer); defer self.allocator.free(buffer);
const results = try self.allocator.alloc(fs.File.PReadError!usize, out.len); const results = try self.allocator.alloc(fs.File.PReadError!usize, out.len);
@ -28,8 +27,11 @@ pub fn ParallelHasher(comptime Hasher: type) type {
defer wg.wait(); defer wg.wait();
for (out, results, 0..) |*out_buf, *result, i| { for (out, results, 0..) |*out_buf, *result, i| {
const fstart = i * chunk_size; const fstart = i * opts.chunk_size;
const fsize = if (fstart + chunk_size > file_size) file_size - fstart else chunk_size; const fsize = if (fstart + opts.chunk_size > file_size)
file_size - fstart
else
opts.chunk_size;
wg.start(); wg.start();
try self.thread_pool.spawn(worker, .{ try self.thread_pool.spawn(worker, .{
file, file,
@ -61,10 +63,11 @@ pub fn ParallelHasher(comptime Hasher: type) type {
}; };
} }
const std = @import("std");
const assert = std.debug.assert; const assert = std.debug.assert;
const fs = std.fs; const fs = std.fs;
const mem = std.mem; const mem = std.mem;
const std = @import("std");
const trace = @import("../tracy.zig").trace;
const Allocator = mem.Allocator; const Allocator = mem.Allocator;
const ThreadPool = std.Thread.Pool; const ThreadPool = std.Thread.Pool;

View File

@ -1,4 +1,14 @@
/// Default path to dyld. const std = @import("std");
const assert = std.debug.assert;
const log = std.log.scoped(.link);
const macho = std.macho;
const mem = std.mem;
const Allocator = mem.Allocator;
const Dylib = @import("Dylib.zig");
const MachO = @import("../MachO.zig");
const Options = @import("../MachO.zig").Options;
pub const default_dyld_path: [*:0]const u8 = "/usr/lib/dyld"; pub const default_dyld_path: [*:0]const u8 = "/usr/lib/dyld";
fn calcInstallNameLen(cmd_size: u64, name: []const u8, assume_max_path_len: bool) u64 { fn calcInstallNameLen(cmd_size: u64, name: []const u8, assume_max_path_len: bool) u64 {
@ -7,31 +17,20 @@ fn calcInstallNameLen(cmd_size: u64, name: []const u8, assume_max_path_len: bool
return mem.alignForward(u64, cmd_size + name_len, @alignOf(u64)); return mem.alignForward(u64, cmd_size + name_len, @alignOf(u64));
} }
const CalcLCsSizeCtx = struct { pub fn calcLoadCommandsSize(macho_file: *MachO, assume_max_path_len: bool) u32 {
segments: []const macho.segment_command_64, const options = &macho_file.options;
dylibs: []const Dylib,
referenced_dylibs: []u16,
wants_function_starts: bool = true,
};
fn calcLCsSize(m: *MachO, ctx: CalcLCsSizeCtx, assume_max_path_len: bool) !u32 {
const comp = m.base.comp;
const gpa = comp.gpa;
var has_text_segment: bool = false;
var sizeofcmds: u64 = 0; var sizeofcmds: u64 = 0;
for (ctx.segments) |seg| {
sizeofcmds += seg.nsects * @sizeOf(macho.section_64) + @sizeOf(macho.segment_command_64); // LC_SEGMENT_64
if (mem.eql(u8, seg.segName(), "__TEXT")) { sizeofcmds += @sizeOf(macho.segment_command_64) * macho_file.segments.items.len;
has_text_segment = true; for (macho_file.segments.items) |seg| {
} sizeofcmds += seg.nsects * @sizeOf(macho.section_64);
} }
// LC_DYLD_INFO_ONLY // LC_DYLD_INFO_ONLY
sizeofcmds += @sizeOf(macho.dyld_info_command); sizeofcmds += @sizeOf(macho.dyld_info_command);
// LC_FUNCTION_STARTS // LC_FUNCTION_STARTS
if (has_text_segment and ctx.wants_function_starts) { sizeofcmds += @sizeOf(macho.linkedit_data_command);
sizeofcmds += @sizeOf(macho.linkedit_data_command);
}
// LC_DATA_IN_CODE // LC_DATA_IN_CODE
sizeofcmds += @sizeOf(macho.linkedit_data_command); sizeofcmds += @sizeOf(macho.linkedit_data_command);
// LC_SYMTAB // LC_SYMTAB
@ -45,15 +44,14 @@ fn calcLCsSize(m: *MachO, ctx: CalcLCsSizeCtx, assume_max_path_len: bool) !u32 {
false, false,
); );
// LC_MAIN // LC_MAIN
if (comp.config.output_mode == .Exe) { if (!options.dylib) {
sizeofcmds += @sizeOf(macho.entry_point_command); sizeofcmds += @sizeOf(macho.entry_point_command);
} }
// LC_ID_DYLIB // LC_ID_DYLIB
if (comp.config.output_mode == .Lib and comp.config.link_mode == .Dynamic) { if (options.dylib) {
sizeofcmds += blk: { sizeofcmds += blk: {
const emit = m.base.emit; const emit = options.emit;
const install_name = m.install_name orelse try emit.directory.join(gpa, &.{emit.sub_path}); const install_name = options.install_name orelse emit.sub_path;
defer if (m.install_name == null) gpa.free(install_name);
break :blk calcInstallNameLen( break :blk calcInstallNameLen(
@sizeOf(macho.dylib_command), @sizeOf(macho.dylib_command),
install_name, install_name,
@ -63,9 +61,7 @@ fn calcLCsSize(m: *MachO, ctx: CalcLCsSizeCtx, assume_max_path_len: bool) !u32 {
} }
// LC_RPATH // LC_RPATH
{ {
var it = RpathIterator.init(gpa, m.base.rpath_list); for (options.rpath_list) |rpath| {
defer it.deinit();
while (try it.next()) |rpath| {
sizeofcmds += calcInstallNameLen( sizeofcmds += calcInstallNameLen(
@sizeOf(macho.rpath_command), @sizeOf(macho.rpath_command),
rpath, rpath,
@ -75,24 +71,22 @@ fn calcLCsSize(m: *MachO, ctx: CalcLCsSizeCtx, assume_max_path_len: bool) !u32 {
} }
// LC_SOURCE_VERSION // LC_SOURCE_VERSION
sizeofcmds += @sizeOf(macho.source_version_command); sizeofcmds += @sizeOf(macho.source_version_command);
// LC_BUILD_VERSION or LC_VERSION_MIN_ or nothing if (options.platform) |platform| {
{
const target = comp.root_mod.resolved_target.result;
const platform = Platform.fromTarget(target);
if (platform.isBuildVersionCompatible()) { if (platform.isBuildVersionCompatible()) {
// LC_BUILD_VERSION // LC_BUILD_VERSION
sizeofcmds += @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version); sizeofcmds += @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version);
} else if (platform.isVersionMinCompatible()) { } else {
// LC_VERSION_MIN_ // LC_VERSION_MIN_*
sizeofcmds += @sizeOf(macho.version_min_command); sizeofcmds += @sizeOf(macho.version_min_command);
} }
} }
// LC_UUID // LC_UUID
sizeofcmds += @sizeOf(macho.uuid_command); sizeofcmds += @sizeOf(macho.uuid_command);
// LC_LOAD_DYLIB // LC_LOAD_DYLIB
for (ctx.referenced_dylibs) |id| { for (macho_file.dylibs.items) |index| {
const dylib = ctx.dylibs[id]; const dylib = macho_file.getFile(index).?.dylib;
const dylib_id = dylib.id orelse unreachable; assert(dylib.isAlive(macho_file));
const dylib_id = dylib.id.?;
sizeofcmds += calcInstallNameLen( sizeofcmds += calcInstallNameLen(
@sizeOf(macho.dylib_command), @sizeOf(macho.dylib_command),
dylib_id.name, dylib_id.name,
@ -100,19 +94,52 @@ fn calcLCsSize(m: *MachO, ctx: CalcLCsSizeCtx, assume_max_path_len: bool) !u32 {
); );
} }
// LC_CODE_SIGNATURE // LC_CODE_SIGNATURE
if (m.requiresCodeSignature()) { if (macho_file.requiresCodeSig()) {
sizeofcmds += @sizeOf(macho.linkedit_data_command); sizeofcmds += @sizeOf(macho.linkedit_data_command);
} }
return @intCast(sizeofcmds); return @as(u32, @intCast(sizeofcmds));
} }
pub fn calcMinHeaderPad(m: *MachO, ctx: CalcLCsSizeCtx) !u64 { pub fn calcLoadCommandsSizeObject(macho_file: *MachO) u32 {
var padding: u32 = (try calcLCsSize(m, ctx, false)) + m.headerpad_size; const options = &macho_file.options;
var sizeofcmds: u64 = 0;
// LC_SEGMENT_64
{
assert(macho_file.segments.items.len == 1);
sizeofcmds += @sizeOf(macho.segment_command_64);
const seg = macho_file.segments.items[0];
sizeofcmds += seg.nsects * @sizeOf(macho.section_64);
}
// LC_DATA_IN_CODE
sizeofcmds += @sizeOf(macho.linkedit_data_command);
// LC_SYMTAB
sizeofcmds += @sizeOf(macho.symtab_command);
// LC_DYSYMTAB
sizeofcmds += @sizeOf(macho.dysymtab_command);
if (options.platform) |platform| {
if (platform.isBuildVersionCompatible()) {
// LC_BUILD_VERSION
sizeofcmds += @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version);
} else {
// LC_VERSION_MIN_*
sizeofcmds += @sizeOf(macho.version_min_command);
}
}
return @as(u32, @intCast(sizeofcmds));
}
pub fn calcMinHeaderPadSize(macho_file: *MachO) u32 {
const options = &macho_file.options;
var padding: u32 = calcLoadCommandsSize(macho_file, false) + (options.headerpad orelse 0);
log.debug("minimum requested headerpad size 0x{x}", .{padding + @sizeOf(macho.mach_header_64)}); log.debug("minimum requested headerpad size 0x{x}", .{padding + @sizeOf(macho.mach_header_64)});
if (m.headerpad_max_install_names) { if (options.headerpad_max_install_names) {
const min_headerpad_size: u32 = try calcLCsSize(m, ctx, true); const min_headerpad_size: u32 = calcLoadCommandsSize(macho_file, true);
log.debug("headerpad_max_install_names minimum headerpad size 0x{x}", .{ log.debug("headerpad_max_install_names minimum headerpad size 0x{x}", .{
min_headerpad_size + @sizeOf(macho.mach_header_64), min_headerpad_size + @sizeOf(macho.mach_header_64),
}); });
@ -125,34 +152,22 @@ pub fn calcMinHeaderPad(m: *MachO, ctx: CalcLCsSizeCtx) !u64 {
return offset; return offset;
} }
pub fn calcNumOfLCs(lc_buffer: []const u8) u32 { pub fn writeDylinkerLC(writer: anytype) !void {
var ncmds: u32 = 0;
var pos: usize = 0;
while (true) {
if (pos >= lc_buffer.len) break;
const cmd = @as(*align(1) const macho.load_command, @ptrCast(lc_buffer.ptr + pos)).*;
ncmds += 1;
pos += cmd.cmdsize;
}
return ncmds;
}
pub fn writeDylinkerLC(lc_writer: anytype) !void {
const name_len = mem.sliceTo(default_dyld_path, 0).len; const name_len = mem.sliceTo(default_dyld_path, 0).len;
const cmdsize = @as(u32, @intCast(mem.alignForward( const cmdsize = @as(u32, @intCast(mem.alignForward(
u64, u64,
@sizeOf(macho.dylinker_command) + name_len, @sizeOf(macho.dylinker_command) + name_len,
@sizeOf(u64), @sizeOf(u64),
))); )));
try lc_writer.writeStruct(macho.dylinker_command{ try writer.writeStruct(macho.dylinker_command{
.cmd = .LOAD_DYLINKER, .cmd = .LOAD_DYLINKER,
.cmdsize = cmdsize, .cmdsize = cmdsize,
.name = @sizeOf(macho.dylinker_command), .name = @sizeOf(macho.dylinker_command),
}); });
try lc_writer.writeAll(mem.sliceTo(default_dyld_path, 0)); try writer.writeAll(mem.sliceTo(default_dyld_path, 0));
const padding = cmdsize - @sizeOf(macho.dylinker_command) - name_len; const padding = cmdsize - @sizeOf(macho.dylinker_command) - name_len;
if (padding > 0) { if (padding > 0) {
try lc_writer.writeByteNTimes(0, padding); try writer.writeByteNTimes(0, padding);
} }
} }
@ -164,14 +179,14 @@ const WriteDylibLCCtx = struct {
compatibility_version: u32 = 0x10000, compatibility_version: u32 = 0x10000,
}; };
fn writeDylibLC(ctx: WriteDylibLCCtx, lc_writer: anytype) !void { pub fn writeDylibLC(ctx: WriteDylibLCCtx, writer: anytype) !void {
const name_len = ctx.name.len + 1; const name_len = ctx.name.len + 1;
const cmdsize = @as(u32, @intCast(mem.alignForward( const cmdsize = @as(u32, @intCast(mem.alignForward(
u64, u64,
@sizeOf(macho.dylib_command) + name_len, @sizeOf(macho.dylib_command) + name_len,
@sizeOf(u64), @sizeOf(u64),
))); )));
try lc_writer.writeStruct(macho.dylib_command{ try writer.writeStruct(macho.dylib_command{
.cmd = ctx.cmd, .cmd = ctx.cmd,
.cmdsize = cmdsize, .cmdsize = cmdsize,
.dylib = .{ .dylib = .{
@ -181,392 +196,75 @@ fn writeDylibLC(ctx: WriteDylibLCCtx, lc_writer: anytype) !void {
.compatibility_version = ctx.compatibility_version, .compatibility_version = ctx.compatibility_version,
}, },
}); });
try lc_writer.writeAll(ctx.name); try writer.writeAll(ctx.name);
try lc_writer.writeByte(0); try writer.writeByte(0);
const padding = cmdsize - @sizeOf(macho.dylib_command) - name_len; const padding = cmdsize - @sizeOf(macho.dylib_command) - name_len;
if (padding > 0) { if (padding > 0) {
try lc_writer.writeByteNTimes(0, padding); try writer.writeByteNTimes(0, padding);
} }
} }
pub fn writeDylibIdLC(macho_file: *MachO, lc_writer: anytype) !void { pub fn writeDylibIdLC(options: *const Options, writer: anytype) !void {
const comp = macho_file.base.comp; assert(options.dylib);
const gpa = comp.gpa; const emit = options.emit;
assert(comp.config.output_mode == .Lib and comp.config.link_mode == .Dynamic); const install_name = options.install_name orelse emit.sub_path;
const emit = macho_file.base.emit; const curr = options.current_version orelse Options.Version.new(1, 0, 0);
const install_name = macho_file.install_name orelse const compat = options.compatibility_version orelse Options.Version.new(1, 0, 0);
try emit.directory.join(gpa, &.{emit.sub_path});
defer if (macho_file.install_name == null) gpa.free(install_name);
const curr = comp.version orelse std.SemanticVersion{
.major = 1,
.minor = 0,
.patch = 0,
};
const compat = macho_file.compatibility_version orelse std.SemanticVersion{
.major = 1,
.minor = 0,
.patch = 0,
};
try writeDylibLC(.{ try writeDylibLC(.{
.cmd = .ID_DYLIB, .cmd = .ID_DYLIB,
.name = install_name, .name = install_name,
.current_version = @as(u32, @intCast(curr.major << 16 | curr.minor << 8 | curr.patch)), .current_version = curr.value,
.compatibility_version = @as(u32, @intCast(compat.major << 16 | compat.minor << 8 | compat.patch)), .compatibility_version = compat.value,
}, lc_writer); }, writer);
} }
const RpathIterator = struct { pub fn writeRpathLCs(rpaths: []const []const u8, writer: anytype) !void {
buffer: []const []const u8, for (rpaths) |rpath| {
table: std.StringHashMap(void),
count: usize = 0,
fn init(gpa: Allocator, rpaths: []const []const u8) RpathIterator {
return .{ .buffer = rpaths, .table = std.StringHashMap(void).init(gpa) };
}
fn deinit(it: *RpathIterator) void {
it.table.deinit();
}
fn next(it: *RpathIterator) !?[]const u8 {
while (true) {
if (it.count >= it.buffer.len) return null;
const rpath = it.buffer[it.count];
it.count += 1;
const gop = try it.table.getOrPut(rpath);
if (gop.found_existing) continue;
return rpath;
}
}
};
pub fn writeRpathLCs(macho_file: *MachO, lc_writer: anytype) !void {
const comp = macho_file.base.comp;
const gpa = comp.gpa;
var it = RpathIterator.init(gpa, macho_file.base.rpath_list);
defer it.deinit();
while (try it.next()) |rpath| {
const rpath_len = rpath.len + 1; const rpath_len = rpath.len + 1;
const cmdsize = @as(u32, @intCast(mem.alignForward( const cmdsize = @as(u32, @intCast(mem.alignForward(
u64, u64,
@sizeOf(macho.rpath_command) + rpath_len, @sizeOf(macho.rpath_command) + rpath_len,
@sizeOf(u64), @sizeOf(u64),
))); )));
try lc_writer.writeStruct(macho.rpath_command{ try writer.writeStruct(macho.rpath_command{
.cmdsize = cmdsize, .cmdsize = cmdsize,
.path = @sizeOf(macho.rpath_command), .path = @sizeOf(macho.rpath_command),
}); });
try lc_writer.writeAll(rpath); try writer.writeAll(rpath);
try lc_writer.writeByte(0); try writer.writeByte(0);
const padding = cmdsize - @sizeOf(macho.rpath_command) - rpath_len; const padding = cmdsize - @sizeOf(macho.rpath_command) - rpath_len;
if (padding > 0) { if (padding > 0) {
try lc_writer.writeByteNTimes(0, padding); try writer.writeByteNTimes(0, padding);
} }
} }
} }
pub fn writeVersionMinLC(platform: Platform, sdk_version: ?std.SemanticVersion, lc_writer: anytype) !void { pub fn writeVersionMinLC(platform: Options.Platform, sdk_version: ?Options.Version, writer: anytype) !void {
const cmd: macho.LC = switch (platform.os_tag) { const cmd: macho.LC = switch (platform.platform) {
.macos => .VERSION_MIN_MACOSX, .MACOS => .VERSION_MIN_MACOSX,
.ios => .VERSION_MIN_IPHONEOS, .IOS, .IOSSIMULATOR => .VERSION_MIN_IPHONEOS,
.tvos => .VERSION_MIN_TVOS, .TVOS, .TVOSSIMULATOR => .VERSION_MIN_TVOS,
.watchos => .VERSION_MIN_WATCHOS, .WATCHOS, .WATCHOSSIMULATOR => .VERSION_MIN_WATCHOS,
else => unreachable, else => unreachable,
}; };
try lc_writer.writeAll(mem.asBytes(&macho.version_min_command{ try writer.writeAll(mem.asBytes(&macho.version_min_command{
.cmd = cmd, .cmd = cmd,
.version = platform.toAppleVersion(), .version = platform.version.value,
.sdk = if (sdk_version) |ver| semanticVersionToAppleVersion(ver) else platform.toAppleVersion(), .sdk = if (sdk_version) |ver| ver.value else platform.version.value,
})); }));
} }
pub fn writeBuildVersionLC(platform: Platform, sdk_version: ?std.SemanticVersion, lc_writer: anytype) !void { pub fn writeBuildVersionLC(platform: Options.Platform, sdk_version: ?Options.Version, writer: anytype) !void {
const cmdsize = @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version); const cmdsize = @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version);
try lc_writer.writeStruct(macho.build_version_command{ try writer.writeStruct(macho.build_version_command{
.cmdsize = cmdsize, .cmdsize = cmdsize,
.platform = platform.toApplePlatform(), .platform = platform.platform,
.minos = platform.toAppleVersion(), .minos = platform.version.value,
.sdk = if (sdk_version) |ver| semanticVersionToAppleVersion(ver) else platform.toAppleVersion(), .sdk = if (sdk_version) |ver| ver.value else platform.version.value,
.ntools = 1, .ntools = 1,
}); });
try lc_writer.writeAll(mem.asBytes(&macho.build_tool_version{ try writer.writeAll(mem.asBytes(&macho.build_tool_version{
.tool = .ZIG, .tool = @as(macho.TOOL, @enumFromInt(0x6)),
.version = 0x0, .version = 0x0,
})); }));
} }
pub fn writeLoadDylibLCs(dylibs: []const Dylib, referenced: []u16, lc_writer: anytype) !void {
for (referenced) |index| {
const dylib = dylibs[index];
const dylib_id = dylib.id orelse unreachable;
try writeDylibLC(.{
.cmd = if (dylib.weak) .LOAD_WEAK_DYLIB else .LOAD_DYLIB,
.name = dylib_id.name,
.timestamp = dylib_id.timestamp,
.current_version = dylib_id.current_version,
.compatibility_version = dylib_id.compatibility_version,
}, lc_writer);
}
}
pub const Platform = struct {
os_tag: std.Target.Os.Tag,
abi: std.Target.Abi,
version: std.SemanticVersion,
/// Using Apple's ld64 as our blueprint, `min_version` as well as `sdk_version` are set to
/// the extracted minimum platform version.
pub fn fromLoadCommand(lc: macho.LoadCommandIterator.LoadCommand) Platform {
switch (lc.cmd()) {
.BUILD_VERSION => {
const cmd = lc.cast(macho.build_version_command).?;
return .{
.os_tag = switch (cmd.platform) {
.MACOS => .macos,
.IOS, .IOSSIMULATOR => .ios,
.TVOS, .TVOSSIMULATOR => .tvos,
.WATCHOS, .WATCHOSSIMULATOR => .watchos,
else => @panic("TODO"),
},
.abi = switch (cmd.platform) {
.IOSSIMULATOR,
.TVOSSIMULATOR,
.WATCHOSSIMULATOR,
=> .simulator,
else => .none,
},
.version = appleVersionToSemanticVersion(cmd.minos),
};
},
.VERSION_MIN_MACOSX,
.VERSION_MIN_IPHONEOS,
.VERSION_MIN_TVOS,
.VERSION_MIN_WATCHOS,
=> {
const cmd = lc.cast(macho.version_min_command).?;
return .{
.os_tag = switch (lc.cmd()) {
.VERSION_MIN_MACOSX => .macos,
.VERSION_MIN_IPHONEOS => .ios,
.VERSION_MIN_TVOS => .tvos,
.VERSION_MIN_WATCHOS => .watchos,
else => unreachable,
},
.abi = .none,
.version = appleVersionToSemanticVersion(cmd.version),
};
},
else => unreachable,
}
}
pub fn fromTarget(target: std.Target) Platform {
return .{
.os_tag = target.os.tag,
.abi = target.abi,
.version = target.os.version_range.semver.min,
};
}
pub fn toAppleVersion(plat: Platform) u32 {
return semanticVersionToAppleVersion(plat.version);
}
pub fn toApplePlatform(plat: Platform) macho.PLATFORM {
return switch (plat.os_tag) {
.macos => .MACOS,
.ios => if (plat.abi == .simulator) .IOSSIMULATOR else .IOS,
.tvos => if (plat.abi == .simulator) .TVOSSIMULATOR else .TVOS,
.watchos => if (plat.abi == .simulator) .WATCHOSSIMULATOR else .WATCHOS,
else => unreachable,
};
}
pub fn isBuildVersionCompatible(plat: Platform) bool {
inline for (supported_platforms) |sup_plat| {
if (sup_plat[0] == plat.os_tag and sup_plat[1] == plat.abi) {
return sup_plat[2] <= plat.toAppleVersion();
}
}
return false;
}
pub fn isVersionMinCompatible(plat: Platform) bool {
inline for (supported_platforms) |sup_plat| {
if (sup_plat[0] == plat.os_tag and sup_plat[1] == plat.abi) {
return sup_plat[3] <= plat.toAppleVersion();
}
}
return false;
}
pub fn fmtTarget(plat: Platform, cpu_arch: std.Target.Cpu.Arch) std.fmt.Formatter(formatTarget) {
return .{ .data = .{ .platform = plat, .cpu_arch = cpu_arch } };
}
const FmtCtx = struct {
platform: Platform,
cpu_arch: std.Target.Cpu.Arch,
};
pub fn formatTarget(
ctx: FmtCtx,
comptime unused_fmt_string: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = unused_fmt_string;
_ = options;
try writer.print("{s}-{s}", .{ @tagName(ctx.cpu_arch), @tagName(ctx.platform.os_tag) });
if (ctx.platform.abi != .none) {
try writer.print("-{s}", .{@tagName(ctx.platform.abi)});
}
}
/// Caller owns the memory.
pub fn allocPrintTarget(plat: Platform, gpa: Allocator, cpu_arch: std.Target.Cpu.Arch) error{OutOfMemory}![]u8 {
var buffer = std.ArrayList(u8).init(gpa);
defer buffer.deinit();
try buffer.writer().print("{}", .{plat.fmtTarget(cpu_arch)});
return buffer.toOwnedSlice();
}
pub fn eqlTarget(plat: Platform, other: Platform) bool {
return plat.os_tag == other.os_tag and plat.abi == other.abi;
}
};
const SupportedPlatforms = struct {
std.Target.Os.Tag,
std.Target.Abi,
u32, // Min platform version for which to emit LC_BUILD_VERSION
u32, // Min supported platform version
};
// Source: https://github.com/apple-oss-distributions/ld64/blob/59a99ab60399c5e6c49e6945a9e1049c42b71135/src/ld/PlatformSupport.cpp#L52
// zig fmt: off
const supported_platforms = [_]SupportedPlatforms{
.{ .macos, .none, 0xA0E00, 0xA0800 },
.{ .ios, .none, 0xC0000, 0x70000 },
.{ .tvos, .none, 0xC0000, 0x70000 },
.{ .watchos, .none, 0x50000, 0x20000 },
.{ .ios, .simulator, 0xD0000, 0x80000 },
.{ .tvos, .simulator, 0xD0000, 0x80000 },
.{ .watchos, .simulator, 0x60000, 0x20000 },
};
// zig fmt: on
inline fn semanticVersionToAppleVersion(version: std.SemanticVersion) u32 {
const major = version.major;
const minor = version.minor;
const patch = version.patch;
return (@as(u32, @intCast(major)) << 16) | (@as(u32, @intCast(minor)) << 8) | @as(u32, @intCast(patch));
}
pub inline fn appleVersionToSemanticVersion(version: u32) std.SemanticVersion {
return .{
.major = @as(u16, @truncate(version >> 16)),
.minor = @as(u8, @truncate(version >> 8)),
.patch = @as(u8, @truncate(version)),
};
}
pub fn inferSdkVersion(macho_file: *MachO) ?std.SemanticVersion {
const comp = macho_file.base.comp;
const gpa = comp.gpa;
var arena_allocator = std.heap.ArenaAllocator.init(gpa);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
const sdk_layout = macho_file.sdk_layout orelse return null;
const sdk_dir = switch (sdk_layout) {
.sdk => comp.sysroot.?,
.vendored => std.fs.path.join(arena, &.{ comp.zig_lib_directory.path.?, "libc", "darwin" }) catch return null,
};
if (readSdkVersionFromSettings(arena, sdk_dir)) |ver| {
return parseSdkVersion(ver);
} else |_| {
// Read from settings should always succeed when vendored.
if (sdk_layout == .vendored) @panic("zig installation bug: unable to parse SDK version");
}
// infer from pathname
const stem = std.fs.path.stem(sdk_dir);
const start = for (stem, 0..) |c, i| {
if (std.ascii.isDigit(c)) break i;
} else stem.len;
const end = for (stem[start..], start..) |c, i| {
if (std.ascii.isDigit(c) or c == '.') continue;
break i;
} else stem.len;
return parseSdkVersion(stem[start..end]);
}
// Official Apple SDKs ship with a `SDKSettings.json` located at the top of SDK fs layout.
// Use property `MinimalDisplayName` to determine version.
// The file/property is also available with vendored libc.
fn readSdkVersionFromSettings(arena: Allocator, dir: []const u8) ![]const u8 {
const sdk_path = try std.fs.path.join(arena, &.{ dir, "SDKSettings.json" });
const contents = try std.fs.cwd().readFileAlloc(arena, sdk_path, std.math.maxInt(u16));
const parsed = try std.json.parseFromSlice(std.json.Value, arena, contents, .{});
if (parsed.value.object.get("MinimalDisplayName")) |ver| return ver.string;
return error.SdkVersionFailure;
}
// Versions reported by Apple aren't exactly semantically valid as they usually omit
// the patch component, so we parse SDK value by hand.
fn parseSdkVersion(raw: []const u8) ?std.SemanticVersion {
var parsed: std.SemanticVersion = .{
.major = 0,
.minor = 0,
.patch = 0,
};
const parseNext = struct {
fn parseNext(it: anytype) ?u16 {
const nn = it.next() orelse return null;
return std.fmt.parseInt(u16, nn, 10) catch null;
}
}.parseNext;
var it = std.mem.splitAny(u8, raw, ".");
parsed.major = parseNext(&it) orelse return null;
parsed.minor = parseNext(&it) orelse return null;
parsed.patch = parseNext(&it) orelse 0;
return parsed;
}
const expect = std.testing.expect;
const expectEqual = std.testing.expectEqual;
fn testParseSdkVersionSuccess(exp: std.SemanticVersion, raw: []const u8) !void {
const maybe_ver = parseSdkVersion(raw);
try expect(maybe_ver != null);
const ver = maybe_ver.?;
try expectEqual(exp.major, ver.major);
try expectEqual(exp.minor, ver.minor);
try expectEqual(exp.patch, ver.patch);
}
test "parseSdkVersion" {
try testParseSdkVersionSuccess(.{ .major = 13, .minor = 4, .patch = 0 }, "13.4");
try testParseSdkVersionSuccess(.{ .major = 13, .minor = 4, .patch = 1 }, "13.4.1");
try testParseSdkVersionSuccess(.{ .major = 11, .minor = 15, .patch = 0 }, "11.15");
try expect(parseSdkVersion("11") == null);
}
const std = @import("std");
const assert = std.debug.assert;
const link = @import("../../link.zig");
const log = std.log.scoped(.link);
const macho = std.macho;
const mem = std.mem;
const Allocator = mem.Allocator;
const Dylib = @import("Dylib.zig");
const MachO = @import("../MachO.zig");
const Compilation = @import("../../Compilation.zig");

View File

@ -0,0 +1,452 @@
pub fn flush(macho_file: *MachO) !void {
markExports(macho_file);
claimUnresolved(macho_file);
try initOutputSections(macho_file);
try macho_file.sortSections();
try macho_file.addAtomsToSections();
try calcSectionSizes(macho_file);
{
// For relocatable, we only ever need a single segment so create it now.
const prot: macho.vm_prot_t = macho.PROT.READ | macho.PROT.WRITE | macho.PROT.EXEC;
try macho_file.segments.append(macho_file.base.allocator, .{
.cmdsize = @sizeOf(macho.segment_command_64),
.segname = MachO.makeStaticString(""),
.maxprot = prot,
.initprot = prot,
});
const seg = &macho_file.segments.items[0];
seg.nsects = @intCast(macho_file.sections.items(.header).len);
seg.cmdsize += seg.nsects * @sizeOf(macho.section_64);
}
var off = try allocateSections(macho_file);
{
// Allocate the single segment.
assert(macho_file.segments.items.len == 1);
const seg = &macho_file.segments.items[0];
var vmaddr: u64 = 0;
var fileoff: u64 = load_commands.calcLoadCommandsSizeObject(macho_file) + @sizeOf(macho.mach_header_64);
seg.vmaddr = vmaddr;
seg.fileoff = fileoff;
for (macho_file.sections.items(.header)) |header| {
vmaddr = header.addr + header.size;
if (!header.isZerofill()) {
fileoff = header.offset + header.size;
}
}
seg.vmsize = vmaddr - seg.vmaddr;
seg.filesize = fileoff - seg.fileoff;
}
macho_file.allocateAtoms();
state_log.debug("{}", .{macho_file.dumpState()});
try macho_file.calcSymtabSize();
try writeAtoms(macho_file);
try writeCompactUnwind(macho_file);
try writeEhFrame(macho_file);
off = mem.alignForward(u32, off, @alignOf(u64));
off = try macho_file.writeDataInCode(0, off);
off = mem.alignForward(u32, off, @alignOf(u64));
off = try macho_file.writeSymtab(off);
off = mem.alignForward(u32, off, @alignOf(u64));
off = try macho_file.writeStrtab(off);
const ncmds, const sizeofcmds = try writeLoadCommands(macho_file);
try writeHeader(macho_file, ncmds, sizeofcmds);
}
fn markExports(macho_file: *MachO) void {
for (macho_file.objects.items) |index| {
for (macho_file.getFile(index).?.getSymbols()) |sym_index| {
const sym = macho_file.getSymbol(sym_index);
const file = sym.getFile(macho_file) orelse continue;
if (sym.visibility != .global) continue;
if (file.getIndex() == index) {
sym.flags.@"export" = true;
}
}
}
}
fn claimUnresolved(macho_file: *MachO) void {
for (macho_file.objects.items) |index| {
const object = macho_file.getFile(index).?.object;
for (object.symbols.items, 0..) |sym_index, i| {
const nlist_idx = @as(Symbol.Index, @intCast(i));
const nlist = object.symtab.items(.nlist)[nlist_idx];
if (!nlist.ext()) continue;
if (!nlist.undf()) continue;
const sym = macho_file.getSymbol(sym_index);
if (sym.getFile(macho_file) != null) continue;
sym.value = 0;
sym.atom = 0;
sym.nlist_idx = nlist_idx;
sym.file = index;
sym.flags.weak_ref = nlist.weakRef();
sym.flags.import = true;
sym.visibility = .global;
}
}
}
fn initOutputSections(macho_file: *MachO) !void {
for (macho_file.objects.items) |index| {
const object = macho_file.getFile(index).?.object;
for (object.atoms.items) |atom_index| {
const atom = macho_file.getAtom(atom_index) orelse continue;
if (!atom.flags.alive) continue;
atom.out_n_sect = try Atom.initOutputSection(atom.getInputSection(macho_file), macho_file);
}
}
const needs_unwind_info = for (macho_file.objects.items) |index| {
if (macho_file.getFile(index).?.object.compact_unwind_sect_index != null) break true;
} else false;
if (needs_unwind_info) {
macho_file.unwind_info_sect_index = try macho_file.addSection("__LD", "__compact_unwind", .{
.flags = macho.S_ATTR_DEBUG,
});
}
const needs_eh_frame = for (macho_file.objects.items) |index| {
if (macho_file.getFile(index).?.object.eh_frame_sect_index != null) break true;
} else false;
if (needs_eh_frame) {
assert(needs_unwind_info);
macho_file.eh_frame_sect_index = try macho_file.addSection("__TEXT", "__eh_frame", .{});
}
}
fn calcSectionSizes(macho_file: *MachO) !void {
const slice = macho_file.sections.slice();
for (slice.items(.header), slice.items(.atoms)) |*header, atoms| {
if (atoms.items.len == 0) continue;
for (atoms.items) |atom_index| {
const atom = macho_file.getAtom(atom_index).?;
const atom_alignment = try math.powi(u32, 2, atom.alignment);
const offset = mem.alignForward(u64, header.size, atom_alignment);
const padding = offset - header.size;
atom.value = offset;
header.size += padding + atom.size;
header.@"align" = @max(header.@"align", atom.alignment);
header.nreloc += atom.calcNumRelocs(macho_file);
}
}
if (macho_file.unwind_info_sect_index) |index| {
calcCompactUnwindSize(macho_file, index);
}
if (macho_file.eh_frame_sect_index) |index| {
const sect = &macho_file.sections.items(.header)[index];
sect.size = try eh_frame.calcSize(macho_file);
sect.@"align" = 3;
sect.nreloc = eh_frame.calcNumRelocs(macho_file);
}
}
fn calcCompactUnwindSize(macho_file: *MachO, sect_index: u8) void {
var size: u32 = 0;
var nreloc: u32 = 0;
for (macho_file.objects.items) |index| {
const object = macho_file.getFile(index).?.object;
for (object.unwind_records.items) |irec| {
const rec = macho_file.getUnwindRecord(irec);
if (!rec.alive) continue;
size += @sizeOf(macho.compact_unwind_entry);
nreloc += 1;
if (rec.getPersonality(macho_file)) |_| {
nreloc += 1;
}
if (rec.getLsdaAtom(macho_file)) |_| {
nreloc += 1;
}
}
}
const sect = &macho_file.sections.items(.header)[sect_index];
sect.size = size;
sect.nreloc = nreloc;
sect.@"align" = 3;
}
fn allocateSections(macho_file: *MachO) !u32 {
var fileoff = load_commands.calcLoadCommandsSizeObject(macho_file) + @sizeOf(macho.mach_header_64);
var vmaddr: u64 = 0;
const slice = macho_file.sections.slice();
for (slice.items(.header)) |*header| {
const alignment = try math.powi(u32, 2, header.@"align");
vmaddr = mem.alignForward(u64, vmaddr, alignment);
header.addr = vmaddr;
vmaddr += header.size;
if (!header.isZerofill()) {
fileoff = mem.alignForward(u32, fileoff, alignment);
header.offset = fileoff;
fileoff += @intCast(header.size);
}
}
for (slice.items(.header)) |*header| {
if (header.nreloc == 0) continue;
header.reloff = mem.alignForward(u32, fileoff, @alignOf(macho.relocation_info));
fileoff = header.reloff + header.nreloc * @sizeOf(macho.relocation_info);
}
return fileoff;
}
// We need to sort relocations in descending order to be compatible with Apple's linker.
fn sortReloc(ctx: void, lhs: macho.relocation_info, rhs: macho.relocation_info) bool {
_ = ctx;
return lhs.r_address > rhs.r_address;
}
fn writeAtoms(macho_file: *MachO) !void {
const tracy = trace(@src());
defer tracy.end();
const gpa = macho_file.base.allocator;
const cpu_arch = macho_file.options.cpu_arch.?;
const slice = macho_file.sections.slice();
for (slice.items(.header), slice.items(.atoms)) |header, atoms| {
if (atoms.items.len == 0) continue;
if (header.isZerofill()) continue;
const code = try gpa.alloc(u8, header.size);
defer gpa.free(code);
const padding_byte: u8 = if (header.isCode() and cpu_arch == .x86_64) 0xcc else 0;
@memset(code, padding_byte);
var relocs = try std.ArrayList(macho.relocation_info).initCapacity(gpa, header.nreloc);
defer relocs.deinit();
for (atoms.items) |atom_index| {
const atom = macho_file.getAtom(atom_index).?;
assert(atom.flags.alive);
const off = atom.value - header.addr;
@memcpy(code[off..][0..atom.size], atom.getCode(macho_file));
try atom.writeRelocs(macho_file, code[off..][0..atom.size], &relocs);
}
assert(relocs.items.len == header.nreloc);
mem.sort(macho.relocation_info, relocs.items, {}, sortReloc);
// TODO scattered writes?
try macho_file.base.file.pwriteAll(code, header.offset);
try macho_file.base.file.pwriteAll(mem.sliceAsBytes(relocs.items), header.reloff);
}
}
fn writeCompactUnwind(macho_file: *MachO) !void {
const sect_index = macho_file.unwind_info_sect_index orelse return;
const gpa = macho_file.base.allocator;
const header = macho_file.sections.items(.header)[sect_index];
const nrecs = @divExact(header.size, @sizeOf(macho.compact_unwind_entry));
var entries = try std.ArrayList(macho.compact_unwind_entry).initCapacity(gpa, nrecs);
defer entries.deinit();
var relocs = try std.ArrayList(macho.relocation_info).initCapacity(gpa, header.nreloc);
defer relocs.deinit();
const addReloc = struct {
fn addReloc(offset: i32, cpu_arch: std.Target.Cpu.Arch) macho.relocation_info {
return .{
.r_address = offset,
.r_symbolnum = 0,
.r_pcrel = 0,
.r_length = 3,
.r_extern = 0,
.r_type = switch (cpu_arch) {
.aarch64 => @intFromEnum(macho.reloc_type_arm64.ARM64_RELOC_UNSIGNED),
.x86_64 => @intFromEnum(macho.reloc_type_x86_64.X86_64_RELOC_UNSIGNED),
else => unreachable,
},
};
}
}.addReloc;
var offset: i32 = 0;
for (macho_file.objects.items) |index| {
const object = macho_file.getFile(index).?.object;
for (object.unwind_records.items) |irec| {
const rec = macho_file.getUnwindRecord(irec);
if (!rec.alive) continue;
var out: macho.compact_unwind_entry = .{
.rangeStart = 0,
.rangeLength = rec.length,
.compactUnwindEncoding = rec.enc.enc,
.personalityFunction = 0,
.lsda = 0,
};
{
// Function address
const atom = rec.getAtom(macho_file);
const addr = rec.getAtomAddress(macho_file);
out.rangeStart = addr;
var reloc = addReloc(offset, macho_file.options.cpu_arch.?);
reloc.r_symbolnum = atom.out_n_sect + 1;
relocs.appendAssumeCapacity(reloc);
}
// Personality function
if (rec.getPersonality(macho_file)) |sym| {
const r_symbolnum = math.cast(u24, sym.getOutputSymtabIndex(macho_file).?) orelse return error.Overflow;
var reloc = addReloc(offset + 16, macho_file.options.cpu_arch.?);
reloc.r_symbolnum = r_symbolnum;
reloc.r_extern = 1;
relocs.appendAssumeCapacity(reloc);
}
// LSDA address
if (rec.getLsdaAtom(macho_file)) |atom| {
const addr = rec.getLsdaAddress(macho_file);
out.lsda = addr;
var reloc = addReloc(offset + 24, macho_file.options.cpu_arch.?);
reloc.r_symbolnum = atom.out_n_sect + 1;
relocs.appendAssumeCapacity(reloc);
}
entries.appendAssumeCapacity(out);
offset += @sizeOf(macho.compact_unwind_entry);
}
}
assert(entries.items.len == nrecs);
assert(relocs.items.len == header.nreloc);
mem.sort(macho.relocation_info, relocs.items, {}, sortReloc);
// TODO scattered writes?
try macho_file.base.file.pwriteAll(mem.sliceAsBytes(entries.items), header.offset);
try macho_file.base.file.pwriteAll(mem.sliceAsBytes(relocs.items), header.reloff);
}
fn writeEhFrame(macho_file: *MachO) !void {
const sect_index = macho_file.eh_frame_sect_index orelse return;
const gpa = macho_file.base.allocator;
const header = macho_file.sections.items(.header)[sect_index];
const code = try gpa.alloc(u8, header.size);
defer gpa.free(code);
var relocs = try std.ArrayList(macho.relocation_info).initCapacity(gpa, header.nreloc);
defer relocs.deinit();
try eh_frame.writeRelocs(macho_file, code, &relocs);
assert(relocs.items.len == header.nreloc);
mem.sort(macho.relocation_info, relocs.items, {}, sortReloc);
// TODO scattered writes?
try macho_file.base.file.pwriteAll(code, header.offset);
try macho_file.base.file.pwriteAll(mem.sliceAsBytes(relocs.items), header.reloff);
}
fn writeLoadCommands(macho_file: *MachO) !struct { usize, usize } {
const gpa = macho_file.base.allocator;
const needed_size = load_commands.calcLoadCommandsSizeObject(macho_file);
const buffer = try gpa.alloc(u8, needed_size);
defer gpa.free(buffer);
var stream = std.io.fixedBufferStream(buffer);
var cwriter = std.io.countingWriter(stream.writer());
const writer = cwriter.writer();
var ncmds: usize = 0;
// Segment and section load commands
{
assert(macho_file.segments.items.len == 1);
const seg = macho_file.segments.items[0];
try writer.writeStruct(seg);
for (macho_file.sections.items(.header)) |header| {
try writer.writeStruct(header);
}
ncmds += 1;
}
try writer.writeStruct(macho_file.data_in_code_cmd);
ncmds += 1;
try writer.writeStruct(macho_file.symtab_cmd);
ncmds += 1;
try writer.writeStruct(macho_file.dysymtab_cmd);
ncmds += 1;
if (macho_file.options.platform) |platform| {
if (platform.isBuildVersionCompatible()) {
try load_commands.writeBuildVersionLC(platform, macho_file.options.sdk_version, writer);
ncmds += 1;
} else {
try load_commands.writeVersionMinLC(platform, macho_file.options.sdk_version, writer);
ncmds += 1;
}
}
assert(cwriter.bytes_written == needed_size);
try macho_file.base.file.pwriteAll(buffer, @sizeOf(macho.mach_header_64));
return .{ ncmds, buffer.len };
}
fn writeHeader(macho_file: *MachO, ncmds: usize, sizeofcmds: usize) !void {
var header: macho.mach_header_64 = .{};
header.filetype = macho.MH_OBJECT;
const subsections_via_symbols = for (macho_file.objects.items) |index| {
const object = macho_file.getFile(index).?.object;
if (object.hasSubsections()) break true;
} else false;
if (subsections_via_symbols) {
header.flags |= macho.MH_SUBSECTIONS_VIA_SYMBOLS;
}
switch (macho_file.options.cpu_arch.?) {
.aarch64 => {
header.cputype = macho.CPU_TYPE_ARM64;
header.cpusubtype = macho.CPU_SUBTYPE_ARM_ALL;
},
.x86_64 => {
header.cputype = macho.CPU_TYPE_X86_64;
header.cpusubtype = macho.CPU_SUBTYPE_X86_64_ALL;
},
else => {},
}
header.ncmds = @intCast(ncmds);
header.sizeofcmds = @intCast(sizeofcmds);
try macho_file.base.file.pwriteAll(mem.asBytes(&header), 0);
}
const assert = std.debug.assert;
const eh_frame = @import("eh_frame.zig");
const load_commands = @import("load_commands.zig");
const macho = std.macho;
const math = std.math;
const mem = std.mem;
const state_log = std.log.scoped(.state);
const std = @import("std");
const trace = @import("../tracy.zig").trace;
const Atom = @import("Atom.zig");
const MachO = @import("../MachO.zig");
const Symbol = @import("Symbol.zig");

View File

@ -1,169 +0,0 @@
pub inline fn stubHelperPreambleSize(cpu_arch: std.Target.Cpu.Arch) u8 {
return switch (cpu_arch) {
.x86_64 => 15,
.aarch64 => 6 * @sizeOf(u32),
else => unreachable, // unhandled architecture type
};
}
pub inline fn stubHelperSize(cpu_arch: std.Target.Cpu.Arch) u8 {
return switch (cpu_arch) {
.x86_64 => 10,
.aarch64 => 3 * @sizeOf(u32),
else => unreachable, // unhandled architecture type
};
}
pub inline fn stubSize(cpu_arch: std.Target.Cpu.Arch) u8 {
return switch (cpu_arch) {
.x86_64 => 6,
.aarch64 => 3 * @sizeOf(u32),
else => unreachable, // unhandled architecture type
};
}
pub inline fn stubAlignment(cpu_arch: std.Target.Cpu.Arch) u8 {
return switch (cpu_arch) {
.x86_64 => 1,
.aarch64 => 4,
else => unreachable, // unhandled architecture type
};
}
pub inline fn stubOffsetInStubHelper(cpu_arch: std.Target.Cpu.Arch) u8 {
return switch (cpu_arch) {
.x86_64 => 1,
.aarch64 => 2 * @sizeOf(u32),
else => unreachable,
};
}
pub fn writeStubHelperPreambleCode(args: struct {
cpu_arch: std.Target.Cpu.Arch,
source_addr: u64,
dyld_private_addr: u64,
dyld_stub_binder_got_addr: u64,
}, writer: anytype) !void {
switch (args.cpu_arch) {
.x86_64 => {
try writer.writeAll(&.{ 0x4c, 0x8d, 0x1d });
{
const disp = try Relocation.calcPcRelativeDisplacementX86(
args.source_addr + 3,
args.dyld_private_addr,
0,
);
try writer.writeInt(i32, disp, .little);
}
try writer.writeAll(&.{ 0x41, 0x53, 0xff, 0x25 });
{
const disp = try Relocation.calcPcRelativeDisplacementX86(
args.source_addr + 11,
args.dyld_stub_binder_got_addr,
0,
);
try writer.writeInt(i32, disp, .little);
}
},
.aarch64 => {
{
const pages = Relocation.calcNumberOfPages(args.source_addr, args.dyld_private_addr);
try writer.writeInt(u32, aarch64.Instruction.adrp(.x17, pages).toU32(), .little);
}
{
const off = try Relocation.calcPageOffset(args.dyld_private_addr, .arithmetic);
try writer.writeInt(u32, aarch64.Instruction.add(.x17, .x17, off, false).toU32(), .little);
}
try writer.writeInt(u32, aarch64.Instruction.stp(
.x16,
.x17,
aarch64.Register.sp,
aarch64.Instruction.LoadStorePairOffset.pre_index(-16),
).toU32(), .little);
{
const pages = Relocation.calcNumberOfPages(args.source_addr + 12, args.dyld_stub_binder_got_addr);
try writer.writeInt(u32, aarch64.Instruction.adrp(.x16, pages).toU32(), .little);
}
{
const off = try Relocation.calcPageOffset(args.dyld_stub_binder_got_addr, .load_store_64);
try writer.writeInt(u32, aarch64.Instruction.ldr(
.x16,
.x16,
aarch64.Instruction.LoadStoreOffset.imm(off),
).toU32(), .little);
}
try writer.writeInt(u32, aarch64.Instruction.br(.x16).toU32(), .little);
},
else => unreachable,
}
}
pub fn writeStubHelperCode(args: struct {
cpu_arch: std.Target.Cpu.Arch,
source_addr: u64,
target_addr: u64,
}, writer: anytype) !void {
switch (args.cpu_arch) {
.x86_64 => {
try writer.writeAll(&.{ 0x68, 0x0, 0x0, 0x0, 0x0, 0xe9 });
{
const disp = try Relocation.calcPcRelativeDisplacementX86(args.source_addr + 6, args.target_addr, 0);
try writer.writeInt(i32, disp, .little);
}
},
.aarch64 => {
const stub_size: u4 = 3 * @sizeOf(u32);
const literal = blk: {
const div_res = try std.math.divExact(u64, stub_size - @sizeOf(u32), 4);
break :blk std.math.cast(u18, div_res) orelse return error.Overflow;
};
try writer.writeInt(u32, aarch64.Instruction.ldrLiteral(
.w16,
literal,
).toU32(), .little);
{
const disp = try Relocation.calcPcRelativeDisplacementArm64(args.source_addr + 4, args.target_addr);
try writer.writeInt(u32, aarch64.Instruction.b(disp).toU32(), .little);
}
try writer.writeAll(&.{ 0x0, 0x0, 0x0, 0x0 });
},
else => unreachable,
}
}
pub fn writeStubCode(args: struct {
cpu_arch: std.Target.Cpu.Arch,
source_addr: u64,
target_addr: u64,
}, writer: anytype) !void {
switch (args.cpu_arch) {
.x86_64 => {
try writer.writeAll(&.{ 0xff, 0x25 });
{
const disp = try Relocation.calcPcRelativeDisplacementX86(args.source_addr + 2, args.target_addr, 0);
try writer.writeInt(i32, disp, .little);
}
},
.aarch64 => {
{
const pages = Relocation.calcNumberOfPages(args.source_addr, args.target_addr);
try writer.writeInt(u32, aarch64.Instruction.adrp(.x16, pages).toU32(), .little);
}
{
const off = try Relocation.calcPageOffset(args.target_addr, .load_store_64);
try writer.writeInt(u32, aarch64.Instruction.ldr(
.x16,
.x16,
aarch64.Instruction.LoadStoreOffset.imm(off),
).toU32(), .little);
}
try writer.writeInt(u32, aarch64.Instruction.br(.x16).toU32(), .little);
},
else => unreachable,
}
}
const std = @import("std");
const aarch64 = @import("../../arch/aarch64/bits.zig");
const Relocation = @import("Relocation.zig");

View File

@ -0,0 +1,669 @@
pub const GotSection = struct {
symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
pub const Index = u32;
pub fn deinit(got: *GotSection, allocator: Allocator) void {
got.symbols.deinit(allocator);
}
pub fn addSymbol(got: *GotSection, sym_index: Symbol.Index, macho_file: *MachO) !void {
const gpa = macho_file.base.allocator;
const index = @as(Index, @intCast(got.symbols.items.len));
const entry = try got.symbols.addOne(gpa);
entry.* = sym_index;
const symbol = macho_file.getSymbol(sym_index);
try symbol.addExtra(.{ .got = index }, macho_file);
}
pub fn getAddress(got: GotSection, index: Index, macho_file: *MachO) u64 {
assert(index < got.symbols.items.len);
const header = macho_file.sections.items(.header)[macho_file.got_sect_index.?];
return header.addr + index * @sizeOf(u64);
}
pub fn size(got: GotSection) usize {
return got.symbols.items.len * @sizeOf(u64);
}
pub fn addDyldRelocs(got: GotSection, macho_file: *MachO) !void {
const tracy = trace(@src());
defer tracy.end();
const gpa = macho_file.base.allocator;
const seg_id = macho_file.sections.items(.segment_id)[macho_file.got_sect_index.?];
const seg = macho_file.segments.items[seg_id];
for (got.symbols.items, 0..) |sym_index, idx| {
const sym = macho_file.getSymbol(sym_index);
const addr = got.getAddress(@intCast(idx), macho_file);
const entry = bind.Entry{
.target = sym_index,
.offset = addr - seg.vmaddr,
.segment_id = seg_id,
.addend = 0,
};
if (sym.flags.import) {
try macho_file.bind.entries.append(gpa, entry);
if (sym.flags.weak) {
try macho_file.weak_bind.entries.append(gpa, entry);
}
} else {
try macho_file.rebase.entries.append(gpa, .{
.offset = addr - seg.vmaddr,
.segment_id = seg_id,
});
if (sym.flags.weak) {
try macho_file.weak_bind.entries.append(gpa, entry);
} else if (sym.flags.interposable) {
try macho_file.bind.entries.append(gpa, entry);
}
}
}
}
pub fn write(got: GotSection, macho_file: *MachO, writer: anytype) !void {
const tracy = trace(@src());
defer tracy.end();
for (got.symbols.items) |sym_index| {
const sym = macho_file.getSymbol(sym_index);
const value = if (sym.flags.import) @as(u64, 0) else sym.getAddress(.{}, macho_file);
try writer.writeInt(u64, value, .little);
}
}
const FormatCtx = struct {
got: GotSection,
macho_file: *MachO,
};
pub fn fmt(got: GotSection, macho_file: *MachO) std.fmt.Formatter(format2) {
return .{ .data = .{ .got = got, .macho_file = macho_file } };
}
pub fn format2(
ctx: FormatCtx,
comptime unused_fmt_string: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = options;
_ = unused_fmt_string;
for (ctx.got.symbols.items, 0..) |entry, i| {
const symbol = ctx.macho_file.getSymbol(entry);
try writer.print(" {d}@0x{x} => {d}@0x{x} ({s})\n", .{
i,
symbol.getGotAddress(ctx.macho_file),
entry,
symbol.getAddress(.{}, ctx.macho_file),
symbol.getName(ctx.macho_file),
});
}
}
};
pub const StubsSection = struct {
symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
pub const Index = u32;
pub fn deinit(stubs: *StubsSection, allocator: Allocator) void {
stubs.symbols.deinit(allocator);
}
pub fn addSymbol(stubs: *StubsSection, sym_index: Symbol.Index, macho_file: *MachO) !void {
const gpa = macho_file.base.allocator;
const index = @as(Index, @intCast(stubs.symbols.items.len));
const entry = try stubs.symbols.addOne(gpa);
entry.* = sym_index;
const symbol = macho_file.getSymbol(sym_index);
try symbol.addExtra(.{ .stubs = index }, macho_file);
}
pub fn getAddress(stubs: StubsSection, index: Index, macho_file: *MachO) u64 {
assert(index < stubs.symbols.items.len);
const header = macho_file.sections.items(.header)[macho_file.stubs_sect_index.?];
return header.addr + index * header.reserved2;
}
pub fn size(stubs: StubsSection, macho_file: *MachO) usize {
const header = macho_file.sections.items(.header)[macho_file.stubs_sect_index.?];
return stubs.symbols.items.len * header.reserved2;
}
pub fn write(stubs: StubsSection, macho_file: *MachO, writer: anytype) !void {
const tracy = trace(@src());
defer tracy.end();
const cpu_arch = macho_file.options.cpu_arch.?;
const laptr_sect = macho_file.sections.items(.header)[macho_file.la_symbol_ptr_sect_index.?];
for (stubs.symbols.items, 0..) |sym_index, idx| {
const sym = macho_file.getSymbol(sym_index);
const source = sym.getAddress(.{ .stubs = true }, macho_file);
const target = laptr_sect.addr + idx * @sizeOf(u64);
switch (cpu_arch) {
.x86_64 => {
try writer.writeAll(&.{ 0xff, 0x25 });
try writer.writeInt(i32, @intCast(target - source - 2 - 4), .little);
},
.aarch64 => {
// TODO relax if possible
const pages = try Relocation.calcNumberOfPages(source, target);
try writer.writeInt(u32, aarch64.Instruction.adrp(.x16, pages).toU32(), .little);
const off = try Relocation.calcPageOffset(target, .load_store_64);
try writer.writeInt(
u32,
aarch64.Instruction.ldr(.x16, .x16, aarch64.Instruction.LoadStoreOffset.imm(off)).toU32(),
.little,
);
try writer.writeInt(u32, aarch64.Instruction.br(.x16).toU32(), .little);
},
else => unreachable,
}
}
}
const FormatCtx = struct {
stubs: StubsSection,
macho_file: *MachO,
};
pub fn fmt(stubs: StubsSection, macho_file: *MachO) std.fmt.Formatter(format2) {
return .{ .data = .{ .stubs = stubs, .macho_file = macho_file } };
}
pub fn format2(
ctx: FormatCtx,
comptime unused_fmt_string: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = options;
_ = unused_fmt_string;
for (ctx.stubs.symbols.items, 0..) |entry, i| {
const symbol = ctx.macho_file.getSymbol(entry);
try writer.print(" {d}@0x{x} => {d}@0x{x} ({s})\n", .{
i,
symbol.getStubsAddress(ctx.macho_file),
entry,
symbol.getAddress(.{}, ctx.macho_file),
symbol.getName(ctx.macho_file),
});
}
}
};
pub const StubsHelperSection = struct {
pub inline fn preambleSize(cpu_arch: std.Target.Cpu.Arch) usize {
return switch (cpu_arch) {
.x86_64 => 15,
.aarch64 => 6 * @sizeOf(u32),
else => 0,
};
}
pub inline fn entrySize(cpu_arch: std.Target.Cpu.Arch) usize {
return switch (cpu_arch) {
.x86_64 => 10,
.aarch64 => 3 * @sizeOf(u32),
else => 0,
};
}
pub fn size(stubs_helper: StubsHelperSection, macho_file: *MachO) usize {
const tracy = trace(@src());
defer tracy.end();
_ = stubs_helper;
const cpu_arch = macho_file.options.cpu_arch.?;
var s: usize = preambleSize(cpu_arch);
for (macho_file.stubs.symbols.items) |sym_index| {
const sym = macho_file.getSymbol(sym_index);
if ((sym.flags.import and !sym.flags.weak) or (!sym.flags.weak and sym.flags.interposable)) {
s += entrySize(cpu_arch);
}
}
return s;
}
pub fn write(stubs_helper: StubsHelperSection, macho_file: *MachO, writer: anytype) !void {
const tracy = trace(@src());
defer tracy.end();
try stubs_helper.writePreamble(macho_file, writer);
const cpu_arch = macho_file.options.cpu_arch.?;
const sect = macho_file.sections.items(.header)[macho_file.stubs_helper_sect_index.?];
const preamble_size = preambleSize(cpu_arch);
const entry_size = entrySize(cpu_arch);
var idx: usize = 0;
for (macho_file.stubs.symbols.items) |sym_index| {
const sym = macho_file.getSymbol(sym_index);
if ((sym.flags.import and !sym.flags.weak) or (!sym.flags.weak and sym.flags.interposable)) {
const offset = macho_file.lazy_bind.offsets.items[idx];
const source: i64 = @intCast(sect.addr + preamble_size + entry_size * idx);
const target: i64 = @intCast(sect.addr);
switch (cpu_arch) {
.x86_64 => {
try writer.writeByte(0x68);
try writer.writeInt(u32, offset, .little);
try writer.writeByte(0xe9);
try writer.writeInt(i32, @intCast(target - source - 6 - 4), .little);
},
.aarch64 => {
const literal = blk: {
const div_res = try std.math.divExact(u64, entry_size - @sizeOf(u32), 4);
break :blk std.math.cast(u18, div_res) orelse return error.Overflow;
};
try writer.writeInt(u32, aarch64.Instruction.ldrLiteral(
.w16,
literal,
).toU32(), .little);
const disp = math.cast(i28, @as(i64, @intCast(target)) - @as(i64, @intCast(source + 4))) orelse
return error.Overflow;
try writer.writeInt(u32, aarch64.Instruction.b(disp).toU32(), .little);
try writer.writeAll(&.{ 0x0, 0x0, 0x0, 0x0 });
},
else => unreachable,
}
idx += 1;
}
}
}
fn writePreamble(stubs_helper: StubsHelperSection, macho_file: *MachO, writer: anytype) !void {
_ = stubs_helper;
const cpu_arch = macho_file.options.cpu_arch.?;
const sect = macho_file.sections.items(.header)[macho_file.stubs_helper_sect_index.?];
const dyld_private_addr = target: {
const sym = macho_file.getSymbol(macho_file.dyld_private_index.?);
break :target sym.getAddress(.{}, macho_file);
};
const dyld_stub_binder_addr = target: {
const sym = macho_file.getSymbol(macho_file.dyld_stub_binder_index.?);
break :target sym.getGotAddress(macho_file);
};
switch (cpu_arch) {
.x86_64 => {
try writer.writeAll(&.{ 0x4c, 0x8d, 0x1d });
try writer.writeInt(i32, @intCast(dyld_private_addr - sect.addr - 3 - 4), .little);
try writer.writeAll(&.{ 0x41, 0x53, 0xff, 0x25 });
try writer.writeInt(i32, @intCast(dyld_stub_binder_addr - sect.addr - 11 - 4), .little);
},
.aarch64 => {
{
// TODO relax if possible
const pages = try Relocation.calcNumberOfPages(sect.addr, dyld_private_addr);
try writer.writeInt(u32, aarch64.Instruction.adrp(.x17, pages).toU32(), .little);
const off = try Relocation.calcPageOffset(dyld_private_addr, .arithmetic);
try writer.writeInt(u32, aarch64.Instruction.add(.x17, .x17, off, false).toU32(), .little);
}
try writer.writeInt(u32, aarch64.Instruction.stp(
.x16,
.x17,
aarch64.Register.sp,
aarch64.Instruction.LoadStorePairOffset.pre_index(-16),
).toU32(), .little);
{
// TODO relax if possible
const pages = try Relocation.calcNumberOfPages(sect.addr + 12, dyld_stub_binder_addr);
try writer.writeInt(u32, aarch64.Instruction.adrp(.x16, pages).toU32(), .little);
const off = try Relocation.calcPageOffset(dyld_stub_binder_addr, .load_store_64);
try writer.writeInt(u32, aarch64.Instruction.ldr(
.x16,
.x16,
aarch64.Instruction.LoadStoreOffset.imm(off),
).toU32(), .little);
}
try writer.writeInt(u32, aarch64.Instruction.br(.x16).toU32(), .little);
},
else => unreachable,
}
}
};
pub const LaSymbolPtrSection = struct {
pub fn size(laptr: LaSymbolPtrSection, macho_file: *MachO) usize {
_ = laptr;
return macho_file.stubs.symbols.items.len * @sizeOf(u64);
}
pub fn addDyldRelocs(laptr: LaSymbolPtrSection, macho_file: *MachO) !void {
const tracy = trace(@src());
defer tracy.end();
_ = laptr;
const gpa = macho_file.base.allocator;
const sect = macho_file.sections.items(.header)[macho_file.la_symbol_ptr_sect_index.?];
const seg_id = macho_file.sections.items(.segment_id)[macho_file.la_symbol_ptr_sect_index.?];
const seg = macho_file.segments.items[seg_id];
for (macho_file.stubs.symbols.items, 0..) |sym_index, idx| {
const sym = macho_file.getSymbol(sym_index);
const addr = sect.addr + idx * @sizeOf(u64);
const entry = bind.Entry{
.target = sym_index,
.offset = addr - seg.vmaddr,
.segment_id = seg_id,
.addend = 0,
};
if (sym.flags.import) {
if (sym.flags.weak) {
try macho_file.bind.entries.append(gpa, entry);
try macho_file.weak_bind.entries.append(gpa, entry);
} else {
try macho_file.lazy_bind.entries.append(gpa, entry);
}
} else {
if (sym.flags.weak) {
try macho_file.rebase.entries.append(gpa, .{
.offset = addr - seg.vmaddr,
.segment_id = seg_id,
});
try macho_file.weak_bind.entries.append(gpa, entry);
} else if (sym.flags.interposable) {
try macho_file.lazy_bind.entries.append(gpa, entry);
}
}
}
}
pub fn write(laptr: LaSymbolPtrSection, macho_file: *MachO, writer: anytype) !void {
const tracy = trace(@src());
defer tracy.end();
_ = laptr;
const cpu_arch = macho_file.options.cpu_arch.?;
const sect = macho_file.sections.items(.header)[macho_file.stubs_helper_sect_index.?];
for (macho_file.stubs.symbols.items, 0..) |sym_index, idx| {
const sym = macho_file.getSymbol(sym_index);
const value: u64 = if (sym.flags.@"export")
sym.getAddress(.{ .stubs = false }, macho_file)
else if (sym.flags.weak)
@as(u64, 0)
else
sect.addr + StubsHelperSection.preambleSize(cpu_arch) +
StubsHelperSection.entrySize(cpu_arch) * idx;
try writer.writeInt(u64, @intCast(value), .little);
}
}
};
pub const TlvPtrSection = struct {
symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
pub const Index = u32;
pub fn deinit(tlv: *TlvPtrSection, allocator: Allocator) void {
tlv.symbols.deinit(allocator);
}
pub fn addSymbol(tlv: *TlvPtrSection, sym_index: Symbol.Index, macho_file: *MachO) !void {
const gpa = macho_file.base.allocator;
const index = @as(Index, @intCast(tlv.symbols.items.len));
const entry = try tlv.symbols.addOne(gpa);
entry.* = sym_index;
const symbol = macho_file.getSymbol(sym_index);
try symbol.addExtra(.{ .tlv_ptr = index }, macho_file);
}
pub fn getAddress(tlv: TlvPtrSection, index: Index, macho_file: *MachO) u64 {
assert(index < tlv.symbols.items.len);
const header = macho_file.sections.items(.header)[macho_file.tlv_ptr_sect_index.?];
return header.addr + index * @sizeOf(u64) * 3;
}
pub fn size(tlv: TlvPtrSection) usize {
return tlv.symbols.items.len * @sizeOf(u64);
}
pub fn addDyldRelocs(tlv: TlvPtrSection, macho_file: *MachO) !void {
const tracy = trace(@src());
defer tracy.end();
const gpa = macho_file.base.allocator;
const seg_id = macho_file.sections.items(.segment_id)[macho_file.tlv_ptr_sect_index.?];
const seg = macho_file.segments.items[seg_id];
for (tlv.symbols.items, 0..) |sym_index, idx| {
const sym = macho_file.getSymbol(sym_index);
const addr = tlv.getAddress(@intCast(idx), macho_file);
const entry = bind.Entry{
.target = sym_index,
.offset = addr - seg.vmaddr,
.segment_id = seg_id,
.addend = 0,
};
if (sym.flags.import) {
try macho_file.bind.entries.append(gpa, entry);
if (sym.flags.weak) {
try macho_file.weak_bind.entries.append(gpa, entry);
}
} else {
try macho_file.rebase.entries.append(gpa, .{
.offset = addr - seg.vmaddr,
.segment_id = seg_id,
});
if (sym.flags.weak) {
try macho_file.weak_bind.entries.append(gpa, entry);
} else if (sym.flags.interposable) {
try macho_file.bind.entries.append(gpa, entry);
}
}
}
}
pub fn write(tlv: TlvPtrSection, macho_file: *MachO, writer: anytype) !void {
const tracy = trace(@src());
defer tracy.end();
for (tlv.symbols.items) |sym_index| {
const sym = macho_file.getSymbol(sym_index);
if (sym.flags.import) {
try writer.writeInt(u64, 0, .little);
} else {
try writer.writeInt(u64, sym.getAddress(.{}, macho_file), .little);
}
}
}
const FormatCtx = struct {
tlv: TlvPtrSection,
macho_file: *MachO,
};
pub fn fmt(tlv: TlvPtrSection, macho_file: *MachO) std.fmt.Formatter(format2) {
return .{ .data = .{ .tlv = tlv, .macho_file = macho_file } };
}
pub fn format2(
ctx: FormatCtx,
comptime unused_fmt_string: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = options;
_ = unused_fmt_string;
for (ctx.tlv.symbols.items, 0..) |entry, i| {
const symbol = ctx.macho_file.getSymbol(entry);
try writer.print(" {d}@0x{x} => {d}@0x{x} ({s})\n", .{
i,
symbol.getTlvPtrAddress(ctx.macho_file),
entry,
symbol.getAddress(.{}, ctx.macho_file),
symbol.getName(ctx.macho_file),
});
}
}
};
pub const ObjcStubsSection = struct {
symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
pub fn deinit(objc: *ObjcStubsSection, allocator: Allocator) void {
objc.symbols.deinit(allocator);
}
pub fn entrySize(cpu_arch: std.Target.Cpu.Arch) u8 {
return switch (cpu_arch) {
.x86_64 => 13,
.aarch64 => 8 * @sizeOf(u32),
else => unreachable,
};
}
pub fn addSymbol(objc: *ObjcStubsSection, sym_index: Symbol.Index, macho_file: *MachO) !void {
const gpa = macho_file.base.allocator;
const index = @as(Index, @intCast(objc.symbols.items.len));
const entry = try objc.symbols.addOne(gpa);
entry.* = sym_index;
const symbol = macho_file.getSymbol(sym_index);
try symbol.addExtra(.{ .objc_stubs = index }, macho_file);
}
pub fn getAddress(objc: ObjcStubsSection, index: Index, macho_file: *MachO) u64 {
assert(index < objc.symbols.items.len);
const header = macho_file.sections.items(.header)[macho_file.objc_stubs_sect_index.?];
return header.addr + index * entrySize(macho_file.options.cpu_arch.?);
}
pub fn size(objc: ObjcStubsSection, macho_file: *MachO) usize {
return objc.symbols.items.len * entrySize(macho_file.options.cpu_arch.?);
}
pub fn write(objc: ObjcStubsSection, macho_file: *MachO, writer: anytype) !void {
const tracy = trace(@src());
defer tracy.end();
for (objc.symbols.items, 0..) |sym_index, idx| {
const sym = macho_file.getSymbol(sym_index);
const addr = objc.getAddress(@intCast(idx), macho_file);
switch (macho_file.options.cpu_arch.?) {
.x86_64 => {
try writer.writeAll(&.{ 0x48, 0x8b, 0x35 });
{
const target = sym.getObjcSelrefsAddress(macho_file);
const source = addr;
try writer.writeInt(i32, @intCast(target - source - 3 - 4), .little);
}
try writer.writeAll(&.{ 0xff, 0x25 });
{
const target_sym = macho_file.getSymbol(macho_file.objc_msg_send_index.?);
const target = target_sym.getGotAddress(macho_file);
const source = addr + 7;
try writer.writeInt(i32, @intCast(target - source - 2 - 4), .little);
}
},
.aarch64 => {
{
const target = sym.getObjcSelrefsAddress(macho_file);
const source = addr;
const pages = try Relocation.calcNumberOfPages(source, target);
try writer.writeInt(u32, aarch64.Instruction.adrp(.x1, pages).toU32(), .little);
const off = try Relocation.calcPageOffset(target, .load_store_64);
try writer.writeInt(
u32,
aarch64.Instruction.ldr(.x1, .x1, aarch64.Instruction.LoadStoreOffset.imm(off)).toU32(),
.little,
);
}
{
const target_sym = macho_file.getSymbol(macho_file.objc_msg_send_index.?);
const target = target_sym.getGotAddress(macho_file);
const source = addr + 2 * @sizeOf(u32);
const pages = try Relocation.calcNumberOfPages(source, target);
try writer.writeInt(u32, aarch64.Instruction.adrp(.x16, pages).toU32(), .little);
const off = try Relocation.calcPageOffset(target, .load_store_64);
try writer.writeInt(
u32,
aarch64.Instruction.ldr(.x16, .x16, aarch64.Instruction.LoadStoreOffset.imm(off)).toU32(),
.little,
);
}
try writer.writeInt(u32, aarch64.Instruction.br(.x16).toU32(), .little);
try writer.writeInt(u32, aarch64.Instruction.brk(1).toU32(), .little);
try writer.writeInt(u32, aarch64.Instruction.brk(1).toU32(), .little);
try writer.writeInt(u32, aarch64.Instruction.brk(1).toU32(), .little);
},
else => unreachable,
}
}
}
const FormatCtx = struct {
objc: ObjcStubsSection,
macho_file: *MachO,
};
pub fn fmt(objc: ObjcStubsSection, macho_file: *MachO) std.fmt.Formatter(format2) {
return .{ .data = .{ .objc = objc, .macho_file = macho_file } };
}
pub fn format2(
ctx: FormatCtx,
comptime unused_fmt_string: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = options;
_ = unused_fmt_string;
for (ctx.objc.symbols.items, 0..) |entry, i| {
const symbol = ctx.macho_file.getSymbol(entry);
try writer.print(" {d}@0x{x} => {d}@0x{x} ({s})\n", .{
i,
symbol.getObjcStubsAddress(ctx.macho_file),
entry,
symbol.getAddress(.{}, ctx.macho_file),
symbol.getName(ctx.macho_file),
});
}
}
pub const Index = u32;
};
pub const Indsymtab = struct {
pub inline fn nsyms(ind: Indsymtab, macho_file: *MachO) u32 {
_ = ind;
return @intCast(macho_file.stubs.symbols.items.len * 2 + macho_file.got.symbols.items.len);
}
pub fn write(ind: Indsymtab, macho_file: *MachO, writer: anytype) !void {
const tracy = trace(@src());
defer tracy.end();
_ = ind;
for (macho_file.stubs.symbols.items) |sym_index| {
const sym = macho_file.getSymbol(sym_index);
try writer.writeInt(u32, sym.getOutputSymtabIndex(macho_file).?, .little);
}
for (macho_file.got.symbols.items) |sym_index| {
const sym = macho_file.getSymbol(sym_index);
try writer.writeInt(u32, sym.getOutputSymtabIndex(macho_file).?, .little);
}
for (macho_file.stubs.symbols.items) |sym_index| {
const sym = macho_file.getSymbol(sym_index);
try writer.writeInt(u32, sym.getOutputSymtabIndex(macho_file).?, .little);
}
}
};
pub const RebaseSection = Rebase;
pub const BindSection = bind.Bind;
pub const WeakBindSection = bind.WeakBind;
pub const LazyBindSection = bind.LazyBind;
pub const ExportTrieSection = Trie;
const aarch64 = @import("../aarch64.zig");
const assert = std.debug.assert;
const bind = @import("dyld_info/bind.zig");
const math = std.math;
const std = @import("std");
const trace = @import("../tracy.zig").trace;
const Allocator = std.mem.Allocator;
const MachO = @import("../MachO.zig");
const Rebase = @import("dyld_info/Rebase.zig");
const Relocation = @import("Relocation.zig");
const Symbol = @import("Symbol.zig");
const Trie = @import("dyld_info/Trie.zig");

View File

@ -1,13 +1,157 @@
//! An algorithm for allocating output machine code section (aka `__TEXT,__text`), pub fn createThunks(sect_id: u8, macho_file: *MachO) !void {
//! and insertion of range extending thunks. As such, this algorithm is only run const tracy = trace(@src());
//! for a target that requires range extenders such as arm64. defer tracy.end();
//!
//! The algorithm works pessimistically and assumes that any reference to an Atom in
//! another output section is out of range.
/// Branch instruction has 26 bits immediate but 4 byte aligned. const gpa = macho_file.base.allocator;
const slice = macho_file.sections.slice();
const header = &slice.items(.header)[sect_id];
const atoms = slice.items(.atoms)[sect_id].items;
assert(atoms.len > 0);
for (atoms) |atom_index| {
macho_file.getAtom(atom_index).?.value = @bitCast(@as(i64, -1));
}
var i: usize = 0;
while (i < atoms.len) {
const start = i;
const start_atom = macho_file.getAtom(atoms[start]).?;
assert(start_atom.flags.alive);
start_atom.value = try advance(header, start_atom.size, start_atom.alignment);
i += 1;
while (i < atoms.len and
header.size - start_atom.value < max_allowed_distance) : (i += 1)
{
const atom_index = atoms[i];
const atom = macho_file.getAtom(atom_index).?;
assert(atom.flags.alive);
atom.value = try advance(header, atom.size, atom.alignment);
}
// Insert a thunk at the group end
const thunk_index = try macho_file.addThunk();
const thunk = macho_file.getThunk(thunk_index);
thunk.out_n_sect = sect_id;
// Scan relocs in the group and create trampolines for any unreachable callsite
for (atoms[start..i]) |atom_index| {
const atom = macho_file.getAtom(atom_index).?;
log.debug("atom({d}) {s}", .{ atom_index, atom.getName(macho_file) });
for (atom.getRelocs(macho_file)) |rel| {
if (rel.type != .branch) continue;
if (isReachable(atom, rel, macho_file)) continue;
try thunk.symbols.put(gpa, rel.target, {});
}
atom.thunk_index = thunk_index;
}
thunk.value = try advance(header, thunk.size(), 2);
log.debug("thunk({d}) : {}", .{ thunk_index, thunk.fmt(macho_file) });
}
}
fn advance(sect: *macho.section_64, size: u64, pow2_align: u32) !u64 {
const alignment = try math.powi(u32, 2, pow2_align);
const offset = mem.alignForward(u64, sect.size, alignment);
const padding = offset - sect.size;
sect.size += padding + size;
sect.@"align" = @max(sect.@"align", pow2_align);
return offset;
}
fn isReachable(atom: *const Atom, rel: Relocation, macho_file: *MachO) bool {
const target = rel.getTargetSymbol(macho_file);
if (target.flags.stubs or target.flags.objc_stubs) return false;
if (atom.out_n_sect != target.out_n_sect) return false;
const target_atom = target.getAtom(macho_file).?;
if (target_atom.value == @as(u64, @bitCast(@as(i64, -1)))) return false;
const saddr = @as(i64, @intCast(atom.value)) + @as(i64, @intCast(rel.offset - atom.off));
const taddr: i64 = @intCast(rel.getTargetAddress(macho_file));
_ = math.cast(i28, taddr + rel.addend - saddr) orelse return false;
return true;
}
pub const Thunk = struct {
value: u64 = 0,
out_n_sect: u8 = 0,
symbols: std.AutoArrayHashMapUnmanaged(Symbol.Index, void) = .{},
pub fn deinit(thunk: *Thunk, allocator: Allocator) void {
thunk.symbols.deinit(allocator);
}
pub fn size(thunk: Thunk) usize {
return thunk.symbols.keys().len * trampoline_size;
}
pub fn getAddress(thunk: Thunk, sym_index: Symbol.Index) u64 {
return thunk.value + thunk.symbols.getIndex(sym_index).? * trampoline_size;
}
pub fn write(thunk: Thunk, macho_file: *MachO, writer: anytype) !void {
for (thunk.symbols.keys(), 0..) |sym_index, i| {
const sym = macho_file.getSymbol(sym_index);
const saddr = thunk.value + i * trampoline_size;
const taddr = sym.getAddress(.{}, macho_file);
const pages = try Relocation.calcNumberOfPages(saddr, taddr);
try writer.writeInt(u32, aarch64.Instruction.adrp(.x16, pages).toU32(), .little);
const off = try Relocation.calcPageOffset(taddr, .arithmetic);
try writer.writeInt(u32, aarch64.Instruction.add(.x16, .x16, off, false).toU32(), .little);
try writer.writeInt(u32, aarch64.Instruction.br(.x16).toU32(), .little);
}
}
pub fn format(
thunk: Thunk,
comptime unused_fmt_string: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = thunk;
_ = unused_fmt_string;
_ = options;
_ = writer;
@compileError("do not format Thunk directly");
}
pub fn fmt(thunk: Thunk, macho_file: *MachO) std.fmt.Formatter(format2) {
return .{ .data = .{
.thunk = thunk,
.macho_file = macho_file,
} };
}
const FormatContext = struct {
thunk: Thunk,
macho_file: *MachO,
};
fn format2(
ctx: FormatContext,
comptime unused_fmt_string: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = options;
_ = unused_fmt_string;
const thunk = ctx.thunk;
const macho_file = ctx.macho_file;
try writer.print("@{x} : size({x})\n", .{ thunk.value, thunk.size() });
for (thunk.symbols.keys()) |index| {
const sym = macho_file.getSymbol(index);
try writer.print(" %{d} : {s} : @{x}\n", .{ index, sym.getName(macho_file), sym.value });
}
}
const trampoline_size = 3 * @sizeOf(u32);
pub const Index = u32;
};
/// Branch instruction has 26 bits immediate but is 4 byte aligned.
const jump_bits = @bitSizeOf(i28); const jump_bits = @bitSizeOf(i28);
const max_distance = (1 << (jump_bits - 1)); const max_distance = (1 << (jump_bits - 1));
/// A branch will need an extender if its target is larger than /// A branch will need an extender if its target is larger than
@ -16,359 +160,17 @@ const max_distance = (1 << (jump_bits - 1));
/// and assume margin to be 5MiB. /// and assume margin to be 5MiB.
const max_allowed_distance = max_distance - 0x500_000; const max_allowed_distance = max_distance - 0x500_000;
pub const Thunk = struct { const aarch64 = @import("../aarch64.zig");
start_index: Atom.Index,
len: u32,
targets: std.MultiArrayList(Target) = .{},
lookup: std.AutoHashMapUnmanaged(Target, u32) = .{},
pub const Tag = enum {
stub,
atom,
};
pub const Target = struct {
tag: Tag,
target: SymbolWithLoc,
};
pub const Index = u32;
pub fn deinit(self: *Thunk, gpa: Allocator) void {
self.targets.deinit(gpa);
self.lookup.deinit(gpa);
}
pub fn getStartAtomIndex(self: Thunk) Atom.Index {
assert(self.len != 0);
return self.start_index;
}
pub fn getEndAtomIndex(self: Thunk) Atom.Index {
assert(self.len != 0);
return self.start_index + self.len - 1;
}
pub fn getSize(self: Thunk) u64 {
return 12 * self.len;
}
pub fn getAlignment() u32 {
return @alignOf(u32);
}
pub fn getTrampoline(self: Thunk, macho_file: *MachO, tag: Tag, target: SymbolWithLoc) ?SymbolWithLoc {
const atom_index = self.lookup.get(.{ .tag = tag, .target = target }) orelse return null;
return macho_file.getAtom(atom_index).getSymbolWithLoc();
}
};
pub fn createThunks(macho_file: *MachO, sect_id: u8) !void {
const header = &macho_file.sections.items(.header)[sect_id];
if (header.size == 0) return;
const comp = macho_file.base.comp;
const gpa = comp.gpa;
const first_atom_index = macho_file.sections.items(.first_atom_index)[sect_id].?;
header.size = 0;
header.@"align" = 0;
var atom_count: u32 = 0;
{
var atom_index = first_atom_index;
while (true) {
const atom = macho_file.getAtom(atom_index);
const sym = macho_file.getSymbolPtr(atom.getSymbolWithLoc());
sym.n_value = 0;
atom_count += 1;
if (atom.next_index) |next_index| {
atom_index = next_index;
} else break;
}
}
var allocated = std.AutoHashMap(Atom.Index, void).init(gpa);
defer allocated.deinit();
try allocated.ensureTotalCapacity(atom_count);
var group_start = first_atom_index;
var group_end = first_atom_index;
var offset: u64 = 0;
while (true) {
const group_start_atom = macho_file.getAtom(group_start);
log.debug("GROUP START at {d}", .{group_start});
while (true) {
const atom = macho_file.getAtom(group_end);
offset = atom.alignment.forward(offset);
const sym = macho_file.getSymbolPtr(atom.getSymbolWithLoc());
sym.n_value = offset;
offset += atom.size;
macho_file.logAtom(group_end, log);
header.@"align" = @max(header.@"align", atom.alignment.toLog2Units());
allocated.putAssumeCapacityNoClobber(group_end, {});
const group_start_sym = macho_file.getSymbol(group_start_atom.getSymbolWithLoc());
if (offset - group_start_sym.n_value >= max_allowed_distance) break;
if (atom.next_index) |next_index| {
group_end = next_index;
} else break;
}
log.debug("GROUP END at {d}", .{group_end});
// Insert thunk at group_end
const thunk_index = @as(u32, @intCast(macho_file.thunks.items.len));
try macho_file.thunks.append(gpa, .{ .start_index = undefined, .len = 0 });
// Scan relocs in the group and create trampolines for any unreachable callsite.
var atom_index = group_start;
while (true) {
const atom = macho_file.getAtom(atom_index);
try scanRelocs(
macho_file,
atom_index,
allocated,
thunk_index,
group_end,
);
if (atom_index == group_end) break;
if (atom.next_index) |next_index| {
atom_index = next_index;
} else break;
}
offset = mem.alignForward(u64, offset, Thunk.getAlignment());
allocateThunk(macho_file, thunk_index, offset, header);
offset += macho_file.thunks.items[thunk_index].getSize();
const thunk = macho_file.thunks.items[thunk_index];
if (thunk.len == 0) {
const group_end_atom = macho_file.getAtom(group_end);
if (group_end_atom.next_index) |next_index| {
group_start = next_index;
group_end = next_index;
} else break;
} else {
const thunk_end_atom_index = thunk.getEndAtomIndex();
const thunk_end_atom = macho_file.getAtom(thunk_end_atom_index);
if (thunk_end_atom.next_index) |next_index| {
group_start = next_index;
group_end = next_index;
} else break;
}
}
header.size = @as(u32, @intCast(offset));
}
fn allocateThunk(
macho_file: *MachO,
thunk_index: Thunk.Index,
base_offset: u64,
header: *macho.section_64,
) void {
const thunk = macho_file.thunks.items[thunk_index];
if (thunk.len == 0) return;
const first_atom_index = thunk.getStartAtomIndex();
const end_atom_index = thunk.getEndAtomIndex();
var atom_index = first_atom_index;
var offset = base_offset;
while (true) {
const atom = macho_file.getAtom(atom_index);
offset = mem.alignForward(u64, offset, Thunk.getAlignment());
const sym = macho_file.getSymbolPtr(atom.getSymbolWithLoc());
sym.n_value = offset;
offset += atom.size;
macho_file.logAtom(atom_index, log);
header.@"align" = @max(header.@"align", atom.alignment.toLog2Units());
if (end_atom_index == atom_index) break;
if (atom.next_index) |next_index| {
atom_index = next_index;
} else break;
}
}
fn scanRelocs(
macho_file: *MachO,
atom_index: Atom.Index,
allocated: std.AutoHashMap(Atom.Index, void),
thunk_index: Thunk.Index,
group_end: Atom.Index,
) !void {
const atom = macho_file.getAtom(atom_index);
const object = macho_file.objects.items[atom.getFile().?];
const base_offset = if (object.getSourceSymbol(atom.sym_index)) |source_sym| blk: {
const source_sect = object.getSourceSection(source_sym.n_sect - 1);
break :blk @as(i32, @intCast(source_sym.n_value - source_sect.addr));
} else 0;
const code = Atom.getAtomCode(macho_file, atom_index);
const relocs = Atom.getAtomRelocs(macho_file, atom_index);
const ctx = Atom.getRelocContext(macho_file, atom_index);
for (relocs) |rel| {
if (!relocNeedsThunk(rel)) continue;
const target = Atom.parseRelocTarget(macho_file, .{
.object_id = atom.getFile().?,
.rel = rel,
.code = code,
.base_offset = ctx.base_offset,
.base_addr = ctx.base_addr,
});
if (isReachable(macho_file, atom_index, rel, base_offset, target, allocated)) continue;
log.debug("{x}: source = {s}@{x}, target = {s}@{x} unreachable", .{
rel.r_address - base_offset,
macho_file.getSymbolName(atom.getSymbolWithLoc()),
macho_file.getSymbol(atom.getSymbolWithLoc()).n_value,
macho_file.getSymbolName(target),
macho_file.getSymbol(target).n_value,
});
const comp = macho_file.base.comp;
const gpa = comp.gpa;
const target_sym = macho_file.getSymbol(target);
const thunk = &macho_file.thunks.items[thunk_index];
const tag: Thunk.Tag = if (target_sym.undf()) .stub else .atom;
const thunk_target: Thunk.Target = .{ .tag = tag, .target = target };
const gop = try thunk.lookup.getOrPut(gpa, thunk_target);
if (!gop.found_existing) {
gop.value_ptr.* = try pushThunkAtom(macho_file, thunk, group_end);
try thunk.targets.append(gpa, thunk_target);
}
try macho_file.thunk_table.put(gpa, atom_index, thunk_index);
}
}
fn pushThunkAtom(macho_file: *MachO, thunk: *Thunk, group_end: Atom.Index) !Atom.Index {
const thunk_atom_index = try createThunkAtom(macho_file);
const thunk_atom = macho_file.getAtomPtr(thunk_atom_index);
const end_atom_index = if (thunk.len == 0) group_end else thunk.getEndAtomIndex();
const end_atom = macho_file.getAtomPtr(end_atom_index);
if (end_atom.next_index) |first_after_index| {
const first_after_atom = macho_file.getAtomPtr(first_after_index);
first_after_atom.prev_index = thunk_atom_index;
thunk_atom.next_index = first_after_index;
}
end_atom.next_index = thunk_atom_index;
thunk_atom.prev_index = end_atom_index;
if (thunk.len == 0) {
thunk.start_index = thunk_atom_index;
}
thunk.len += 1;
return thunk_atom_index;
}
inline fn relocNeedsThunk(rel: macho.relocation_info) bool {
const rel_type = @as(macho.reloc_type_arm64, @enumFromInt(rel.r_type));
return rel_type == .ARM64_RELOC_BRANCH26;
}
fn isReachable(
macho_file: *MachO,
atom_index: Atom.Index,
rel: macho.relocation_info,
base_offset: i32,
target: SymbolWithLoc,
allocated: std.AutoHashMap(Atom.Index, void),
) bool {
if (macho_file.stub_table.lookup.contains(target)) return false;
const source_atom = macho_file.getAtom(atom_index);
const source_sym = macho_file.getSymbol(source_atom.getSymbolWithLoc());
const target_object = macho_file.objects.items[target.getFile().?];
const target_atom_index = target_object.getAtomIndexForSymbol(target.sym_index).?;
const target_atom = macho_file.getAtom(target_atom_index);
const target_sym = macho_file.getSymbol(target_atom.getSymbolWithLoc());
if (source_sym.n_sect != target_sym.n_sect) return false;
if (!allocated.contains(target_atom_index)) return false;
const source_addr = source_sym.n_value + @as(u32, @intCast(rel.r_address - base_offset));
const target_addr = if (Atom.relocRequiresGot(macho_file, rel))
macho_file.getGotEntryAddress(target).?
else
Atom.getRelocTargetAddress(macho_file, target, false);
_ = Relocation.calcPcRelativeDisplacementArm64(source_addr, target_addr) catch
return false;
return true;
}
fn createThunkAtom(macho_file: *MachO) !Atom.Index {
const sym_index = try macho_file.allocateSymbol();
const atom_index = try macho_file.createAtom(sym_index, .{
.size = @sizeOf(u32) * 3,
.alignment = .@"4",
});
const sym = macho_file.getSymbolPtr(.{ .sym_index = sym_index });
sym.n_type = macho.N_SECT;
sym.n_sect = macho_file.text_section_index.? + 1;
return atom_index;
}
pub fn writeThunkCode(macho_file: *MachO, thunk: *const Thunk, writer: anytype) !void {
const slice = thunk.targets.slice();
for (thunk.getStartAtomIndex()..thunk.getEndAtomIndex(), 0..) |atom_index, target_index| {
const atom = macho_file.getAtom(@intCast(atom_index));
const sym = macho_file.getSymbol(atom.getSymbolWithLoc());
const source_addr = sym.n_value;
const tag = slice.items(.tag)[target_index];
const target = slice.items(.target)[target_index];
const target_addr = switch (tag) {
.stub => macho_file.getStubsEntryAddress(target).?,
.atom => macho_file.getSymbol(target).n_value,
};
const pages = Relocation.calcNumberOfPages(source_addr, target_addr);
try writer.writeInt(u32, aarch64.Instruction.adrp(.x16, pages).toU32(), .little);
const off = try Relocation.calcPageOffset(target_addr, .arithmetic);
try writer.writeInt(u32, aarch64.Instruction.add(.x16, .x16, off, false).toU32(), .little);
try writer.writeInt(u32, aarch64.Instruction.br(.x16).toU32(), .little);
}
}
const std = @import("std");
const assert = std.debug.assert; const assert = std.debug.assert;
const log = std.log.scoped(.thunks); const log = std.log.scoped(.link);
const macho = std.macho; const macho = std.macho;
const math = std.math; const math = std.math;
const mem = std.mem; const mem = std.mem;
const std = @import("std");
const aarch64 = @import("../../arch/aarch64/bits.zig"); const trace = @import("../tracy.zig").trace;
const Allocator = mem.Allocator; const Allocator = mem.Allocator;
const Atom = @import("Atom.zig"); const Atom = @import("Atom.zig");
const MachO = @import("../MachO.zig"); const MachO = @import("../MachO.zig");
const Relocation = @import("Relocation.zig"); const Relocation = @import("Relocation.zig");
const SymbolWithLoc = MachO.SymbolWithLoc; const Symbol = @import("Symbol.zig");

View File

@ -4,22 +4,31 @@
/// and we will use it too as it seems accepted by Apple OSes. /// and we will use it too as it seems accepted by Apple OSes.
/// TODO LLD also hashes the output filename to disambiguate between same builds with different /// TODO LLD also hashes the output filename to disambiguate between same builds with different
/// output files. Should we also do that? /// output files. Should we also do that?
pub fn calcUuid(comp: *const Compilation, file: fs.File, file_size: u64, out: *[Md5.digest_length]u8) !void { pub fn calcUuid(
allocator: Allocator,
thread_pool: *ThreadPool,
file: fs.File,
file_size: u64,
out: *[Md5.digest_length]u8,
) !void {
const tracy = trace(@src());
defer tracy.end();
const chunk_size: usize = 1024 * 1024; const chunk_size: usize = 1024 * 1024;
const num_chunks: usize = std.math.cast(usize, @divTrunc(file_size, chunk_size)) orelse return error.Overflow; const num_chunks: usize = std.math.cast(usize, @divTrunc(file_size, chunk_size)) orelse return error.Overflow;
const actual_num_chunks = if (@rem(file_size, chunk_size) > 0) num_chunks + 1 else num_chunks; const actual_num_chunks = if (@rem(file_size, chunk_size) > 0) num_chunks + 1 else num_chunks;
const hashes = try comp.gpa.alloc([Md5.digest_length]u8, actual_num_chunks); const hashes = try allocator.alloc([Md5.digest_length]u8, actual_num_chunks);
defer comp.gpa.free(hashes); defer allocator.free(hashes);
var hasher = Hasher(Md5){ .allocator = comp.gpa, .thread_pool = comp.thread_pool }; var hasher = Hasher(Md5){ .allocator = allocator, .thread_pool = thread_pool };
try hasher.hash(file, hashes, .{ try hasher.hash(file, hashes, .{
.chunk_size = chunk_size, .chunk_size = chunk_size,
.max_file_size = file_size, .max_file_size = file_size,
}); });
const final_buffer = try comp.gpa.alloc(u8, actual_num_chunks * Md5.digest_length); const final_buffer = try allocator.alloc(u8, actual_num_chunks * Md5.digest_length);
defer comp.gpa.free(final_buffer); defer allocator.free(final_buffer);
for (hashes, 0..) |hash, i| { for (hashes, 0..) |hash, i| {
@memcpy(final_buffer[i * Md5.digest_length ..][0..Md5.digest_length], &hash); @memcpy(final_buffer[i * Md5.digest_length ..][0..Md5.digest_length], &hash);
@ -35,11 +44,12 @@ inline fn conform(out: *[Md5.digest_length]u8) void {
out[8] = (out[8] & 0x3F) | 0x80; out[8] = (out[8] & 0x3F) | 0x80;
} }
const std = @import("std");
const fs = std.fs; const fs = std.fs;
const mem = std.mem; const mem = std.mem;
const std = @import("std");
const trace = @import("../tracy.zig").trace;
const Allocator = mem.Allocator; const Allocator = mem.Allocator;
const Compilation = @import("../../Compilation.zig");
const Md5 = std.crypto.hash.Md5; const Md5 = std.crypto.hash.Md5;
const Hasher = @import("hasher.zig").ParallelHasher; const Hasher = @import("hasher.zig").ParallelHasher;
const ThreadPool = std.Thread.Pool;

File diff suppressed because it is too large Load Diff