mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 14:23:09 +00:00
macho: copy over new implementation sources from zld
This commit is contained in:
parent
92211135f1
commit
2f94dc939e
@ -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"
|
||||||
|
|||||||
@ -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
@ -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);
|
||||||
|
}
|
||||||
|
|||||||
@ -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
249
src/link/MachO/InternalObject.zig
Normal file
249
src/link/MachO/InternalObject.zig
Normal 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
@ -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
383
src/link/MachO/Symbol.zig
Normal 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
@ -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);
|
|
||||||
|
|||||||
@ -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;
|
|
||||||
|
|||||||
@ -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
@ -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
116
src/link/MachO/file.zig
Normal 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");
|
||||||
@ -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;
|
||||||
|
|||||||
@ -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");
|
|
||||||
|
|||||||
452
src/link/MachO/relocatable.zig
Normal file
452
src/link/MachO/relocatable.zig
Normal 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");
|
||||||
@ -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");
|
|
||||||
669
src/link/MachO/synthetic.zig
Normal file
669
src/link/MachO/synthetic.zig
Normal 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");
|
||||||
@ -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");
|
||||||
|
|||||||
@ -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
Loading…
x
Reference in New Issue
Block a user