Merge pull request #17020 from ziglang/macho-versions

macho: big-ish refactor and report errors to the user using Zig's API
This commit is contained in:
Jakub Konka 2023-08-31 07:50:29 +02:00 committed by GitHub
commit f4c9e19bc3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 5632 additions and 6756 deletions

View File

@ -588,7 +588,6 @@ set(ZIG_STAGE2_SOURCES
"${CMAKE_SOURCE_DIR}/src/link/MachO/Relocation.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/Trie.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/UnwindInfo.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/ZldAtom.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/dead_strip.zig"

View File

@ -2609,6 +2609,9 @@ pub fn totalErrorCount(self: *Compilation) u32 {
}
total += @intFromBool(self.link_error_flags.missing_libc);
// Misc linker errors
total += self.bin_file.miscErrors().len;
// Compile log errors only count if there are no other errors.
if (total == 0) {
if (self.bin_file.options.module) |module| {
@ -2759,6 +2762,19 @@ pub fn getAllErrorsAlloc(self: *Compilation) !ErrorBundle {
}));
}
for (self.bin_file.miscErrors()) |link_err| {
try bundle.addRootErrorMessage(.{
.msg = try bundle.addString(link_err.msg),
.notes_len = @intCast(link_err.notes.len),
});
const notes_start = try bundle.reserveNotes(@intCast(link_err.notes.len));
for (link_err.notes, 0..) |note, i| {
bundle.extra.items[notes_start + i] = @intFromEnum(try bundle.addErrorMessage(.{
.msg = try bundle.addString(note.msg),
}));
}
}
if (self.bin_file.options.module) |module| {
if (bundle.root_list.items.len == 0 and module.compile_log_decls.count() != 0) {
const keys = module.compile_log_decls.keys();

View File

@ -670,7 +670,7 @@ fn mirCallExtern(emit: *Emit, inst: Mir.Inst.Index) !void {
if (emit.bin_file.cast(link.File.MachO)) |macho_file| {
// Add relocation to the decl.
const atom_index = macho_file.getAtomIndexForSymbol(.{ .sym_index = relocation.atom_index, .file = null }).?;
const atom_index = macho_file.getAtomIndexForSymbol(.{ .sym_index = relocation.atom_index }).?;
const target = macho_file.getGlobalByIndex(relocation.sym_index);
try link.File.MachO.Atom.addRelocation(macho_file, atom_index, .{
.type = .branch,
@ -885,9 +885,9 @@ fn mirLoadMemoryPie(emit: *Emit, inst: Mir.Inst.Index) !void {
if (emit.bin_file.cast(link.File.MachO)) |macho_file| {
const Atom = link.File.MachO.Atom;
const Relocation = Atom.Relocation;
const atom_index = macho_file.getAtomIndexForSymbol(.{ .sym_index = data.atom_index, .file = null }).?;
const atom_index = macho_file.getAtomIndexForSymbol(.{ .sym_index = data.atom_index }).?;
try Atom.addRelocations(macho_file, atom_index, &[_]Relocation{ .{
.target = .{ .sym_index = data.sym_index, .file = null },
.target = .{ .sym_index = data.sym_index },
.offset = offset,
.addend = 0,
.pcrel = true,
@ -898,7 +898,7 @@ fn mirLoadMemoryPie(emit: *Emit, inst: Mir.Inst.Index) !void {
else => unreachable,
},
}, .{
.target = .{ .sym_index = data.sym_index, .file = null },
.target = .{ .sym_index = data.sym_index },
.offset = offset + 4,
.addend = 0,
.pcrel = false,

View File

@ -43,9 +43,7 @@ pub fn emitMir(emit: *Emit) Error!void {
}),
.linker_extern_fn => |symbol| if (emit.bin_file.cast(link.File.MachO)) |macho_file| {
// Add relocation to the decl.
const atom_index = macho_file.getAtomIndexForSymbol(
.{ .sym_index = symbol.atom_index, .file = null },
).?;
const atom_index = macho_file.getAtomIndexForSymbol(.{ .sym_index = symbol.atom_index }).?;
const target = macho_file.getGlobalByIndex(symbol.sym_index);
try link.File.MachO.Atom.addRelocation(macho_file, atom_index, .{
.type = .branch,
@ -77,10 +75,7 @@ pub fn emitMir(emit: *Emit) Error!void {
.linker_import,
.linker_tlv,
=> |symbol| if (emit.bin_file.cast(link.File.MachO)) |macho_file| {
const atom_index = macho_file.getAtomIndexForSymbol(.{
.sym_index = symbol.atom_index,
.file = null,
}).?;
const atom_index = macho_file.getAtomIndexForSymbol(.{ .sym_index = symbol.atom_index }).?;
try link.File.MachO.Atom.addRelocation(macho_file, atom_index, .{
.type = switch (lowered_relocs[0].target) {
.linker_got => .got,
@ -88,7 +83,7 @@ pub fn emitMir(emit: *Emit) Error!void {
.linker_tlv => .tlv,
else => unreachable,
},
.target = .{ .sym_index = symbol.sym_index, .file = null },
.target = .{ .sym_index = symbol.sym_index },
.offset = @as(u32, @intCast(end_offset - 4)),
.addend = 0,
.pcrel = true,

View File

@ -228,7 +228,6 @@ pub const Options = struct {
version: ?std.SemanticVersion,
compatibility_version: ?std.SemanticVersion,
darwin_sdk_version: ?std.SemanticVersion = null,
libc_installation: ?*const LibCInstallation,
dwarf_format: ?std.dwarf.Format,
@ -694,19 +693,15 @@ pub const File = struct {
/// TODO audit this error set. most of these should be collapsed into one error,
/// and ErrorFlags should be updated to convey the meaning to the user.
pub const FlushError = error{
BadDwarfCfi,
CacheUnavailable,
CurrentWorkingDirectoryUnlinked,
DivisionByZero,
DllImportLibraryNotFound,
EmptyStubFile,
ExpectedFuncType,
FailedToEmit,
FailedToResolveRelocationTarget,
FileSystem,
FilesOpenedWithWrongFlags,
FlushFailure,
FrameworkNotFound,
FunctionSignatureMismatch,
GlobalTypeMismatch,
HotSwapUnavailableOnHostOperatingSystem,
@ -723,27 +718,19 @@ pub const File = struct {
LLD_LinkingIsTODO_ForSpirV,
LibCInstallationMissingCRTDir,
LibCInstallationNotAvailable,
LibraryNotFound,
LinkingWithoutZigSourceUnimplemented,
MalformedArchive,
MalformedDwarf,
MalformedSection,
MemoryTooBig,
MemoryTooSmall,
MismatchedCpuArchitecture,
MissAlignment,
MissingEndForBody,
MissingEndForExpression,
/// TODO: this should be removed from the error set in favor of using ErrorFlags
MissingMainEntrypoint,
/// TODO: this should be removed from the error set in favor of using ErrorFlags
MissingSection,
MissingSymbol,
MissingTableSymbols,
ModuleNameMismatch,
MultipleSymbolDefinitions,
NoObjectsToLink,
NotObject,
NotObjectFile,
NotSupported,
OutOfMemory,
@ -755,21 +742,15 @@ pub const File = struct {
SymbolMismatchingType,
TODOImplementPlan9Objs,
TODOImplementWritingLibFiles,
TODOImplementWritingStaticLibFiles,
UnableToSpawnSelf,
UnableToSpawnWasm,
UnableToWriteArchive,
UndefinedLocal,
/// TODO: merge with UndefinedSymbolReference
UndefinedSymbol,
/// TODO: merge with UndefinedSymbol
UndefinedSymbolReference,
Underflow,
UnexpectedRemainder,
UnexpectedTable,
UnexpectedValue,
UnhandledDwFormValue,
UnhandledSymbolType,
UnknownFeature,
Unseekable,
UnsupportedCpuArchitecture,
@ -866,6 +847,13 @@ pub const File = struct {
}
}
pub fn miscErrors(base: *File) []const ErrorMsg {
switch (base.tag) {
.macho => return @fieldParentPtr(MachO, "base", base).misc_errors.items,
else => return &.{},
}
}
pub const UpdateDeclExportsError = error{
OutOfMemory,
AnalysisFail,
@ -1129,6 +1117,19 @@ pub const File = struct {
missing_libc: bool = false,
};
pub const ErrorMsg = struct {
msg: []const u8,
notes: []ErrorMsg = &.{},
pub fn deinit(self: *ErrorMsg, gpa: Allocator) void {
for (self.notes) |*note| {
note.deinit(gpa);
}
gpa.free(self.notes);
gpa.free(self.msg);
}
};
pub const LazySymbol = struct {
pub const Kind = enum { code, const_data };

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,3 @@
const Archive = @This();
const std = @import("std");
const assert = std.debug.assert;
const fs = std.fs;
const log = std.log.scoped(.link);
const macho = std.macho;
const mem = std.mem;
const Allocator = mem.Allocator;
const Object = @import("Object.zig");
file: fs.File,
fat_offset: u64,
name: []const u8,
@ -87,6 +75,13 @@ const ar_hdr = extern struct {
}
};
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 {
self.file.close();
for (self.toc.keys()) |*key| {
@ -100,21 +95,8 @@ pub fn deinit(self: *Archive, allocator: Allocator) void {
}
pub fn parse(self: *Archive, allocator: Allocator, reader: anytype) !void {
const magic = try reader.readBytesNoEof(SARMAG);
if (!mem.eql(u8, &magic, ARMAG)) {
log.debug("invalid magic: expected '{s}', found '{s}'", .{ ARMAG, magic });
return error.NotArchive;
}
_ = try reader.readBytesNoEof(SARMAG);
self.header = try reader.readStruct(ar_hdr);
if (!mem.eql(u8, &self.header.ar_fmag, ARFMAG)) {
log.debug("invalid header delimiter: expected '{s}', found '{s}'", .{
ARFMAG,
self.header.ar_fmag,
});
return error.NotArchive;
}
const name_or_length = try self.header.nameOrLength();
var embedded_name = try parseName(allocator, name_or_length, reader);
log.debug("parsing archive '{s}' at '{s}'", .{ embedded_name, self.name });
@ -146,7 +128,7 @@ fn parseTableOfContents(self: *Archive, allocator: Allocator, reader: anytype) !
defer allocator.free(symtab);
reader.readNoEof(symtab) catch {
log.err("incomplete symbol table: expected symbol table of length 0x{x}", .{symtab_size});
log.debug("incomplete symbol table: expected symbol table of length 0x{x}", .{symtab_size});
return error.MalformedArchive;
};
@ -155,7 +137,7 @@ fn parseTableOfContents(self: *Archive, allocator: Allocator, reader: anytype) !
defer allocator.free(strtab);
reader.readNoEof(strtab) catch {
log.err("incomplete symbol table: expected string table of length 0x{x}", .{strtab_size});
log.debug("incomplete symbol table: expected string table of length 0x{x}", .{strtab_size});
return error.MalformedArchive;
};
@ -182,22 +164,12 @@ fn parseTableOfContents(self: *Archive, allocator: Allocator, reader: anytype) !
}
}
pub fn parseObject(
self: Archive,
gpa: Allocator,
cpu_arch: std.Target.Cpu.Arch,
offset: u32,
) !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);
if (!mem.eql(u8, &object_header.ar_fmag, ARFMAG)) {
log.err("invalid header delimiter: expected '{s}', found '{s}'", .{ ARFMAG, object_header.ar_fmag });
return error.MalformedArchive;
}
const name_or_length = try object_header.nameOrLength();
const object_name = try parseName(gpa, name_or_length, reader);
defer gpa.free(object_name);
@ -227,7 +199,19 @@ pub fn parseObject(
.contents = contents,
};
try object.parse(gpa, cpu_arch);
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 macho = std.macho;
const mem = std.mem;
const Allocator = mem.Allocator;
const Object = @import("Object.zig");

File diff suppressed because it is too large Load Diff

View File

@ -1,17 +1,175 @@
const CodeSignature = @This();
page_size: u16,
code_directory: CodeDirectory,
requirements: ?Requirements = null,
entitlements: ?Entitlements = null,
signature: ?Signature = null,
const std = @import("std");
const assert = std.debug.assert;
const fs = std.fs;
const log = std.log.scoped(.link);
const macho = std.macho;
const mem = std.mem;
const testing = std.testing;
pub fn init(page_size: u16) CodeSignature {
return .{
.page_size = page_size,
.code_directory = CodeDirectory.init(page_size),
};
}
const Allocator = mem.Allocator;
const Compilation = @import("../../Compilation.zig");
const Hasher = @import("hasher.zig").ParallelHasher;
const Sha256 = std.crypto.hash.sha2.Sha256;
pub fn deinit(self: *CodeSignature, allocator: Allocator) void {
self.code_directory.deinit(allocator);
if (self.requirements) |*req| {
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,
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.writeIntBig(u32, header.magic);
try writer.writeIntBig(u32, header.length);
try writer.writeIntBig(u32, header.count);
var offset: u32 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) * @as(u32, @intCast(blobs.items.len));
for (blobs.items) |blob| {
try writer.writeIntBig(u32, blob.slotType());
try writer.writeIntBig(u32, offset);
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;
@ -218,175 +376,17 @@ const Signature = struct {
}
};
page_size: u16,
code_directory: CodeDirectory,
requirements: ?Requirements = null,
entitlements: ?Entitlements = null,
signature: ?Signature = null,
const CodeSignature = @This();
pub fn init(page_size: u16) CodeSignature {
return .{
.page_size = page_size,
.code_directory = CodeDirectory.init(page_size),
};
}
const std = @import("std");
const assert = std.debug.assert;
const fs = std.fs;
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 {
self.code_directory.deinit(allocator);
if (self.requirements) |*req| {
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,
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.writeIntBig(u32, header.magic);
try writer.writeIntBig(u32, header.length);
try writer.writeIntBig(u32, header.count);
var offset: u32 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) * @as(u32, @intCast(blobs.items.len));
for (blobs.items) |blob| {
try writer.writeIntBig(u32, blob.slotType());
try writer.writeIntBig(u32, offset);
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 Allocator = mem.Allocator;
const Compilation = @import("../../Compilation.zig");
const Hasher = @import("hasher.zig").ParallelHasher;
const Sha256 = std.crypto.hash.sha2.Sha256;

View File

@ -1,30 +1,6 @@
const DebugSymbols = @This();
const std = @import("std");
const build_options = @import("build_options");
const assert = std.debug.assert;
const fs = std.fs;
const link = @import("../../link.zig");
const load_commands = @import("load_commands.zig");
const log = std.log.scoped(.dsym);
const macho = std.macho;
const makeStaticString = MachO.makeStaticString;
const math = std.math;
const mem = std.mem;
const padToIdeal = MachO.padToIdeal;
const trace = @import("../../tracy.zig").trace;
const Allocator = mem.Allocator;
const Dwarf = @import("../Dwarf.zig");
const MachO = @import("../MachO.zig");
const Module = @import("../../Module.zig");
const StringTable = @import("../strtab.zig").StringTable;
const Type = @import("../../type.zig").Type;
allocator: Allocator,
dwarf: Dwarf,
file: fs.File,
page_size: u16,
symtab_cmd: macho.symtab_command = .{},
@ -62,13 +38,14 @@ pub const Reloc = struct {
/// You must call this function *after* `MachO.populateMissingMetadata()`
/// has been called to get a viable debug symbols output.
pub fn populateMissingMetadata(self: *DebugSymbols) !void {
pub fn populateMissingMetadata(self: *DebugSymbols, macho_file: *MachO) !void {
if (self.dwarf_segment_cmd_index == null) {
self.dwarf_segment_cmd_index = @as(u8, @intCast(self.segments.items.len));
const off = @as(u64, @intCast(self.page_size));
const page_size = MachO.getPageSize(macho_file.base.options.target.cpu.arch);
const off = @as(u64, @intCast(page_size));
const ideal_size: u16 = 200 + 128 + 160 + 250;
const needed_size = mem.alignForward(u64, padToIdeal(ideal_size), self.page_size);
const needed_size = mem.alignForward(u64, padToIdeal(ideal_size), page_size);
log.debug("found __DWARF segment free space 0x{x} to 0x{x}", .{ off, off + needed_size });
@ -355,7 +332,8 @@ fn finalizeDwarfSegment(self: *DebugSymbols, macho_file: *MachO) void {
file_size = @max(file_size, header.offset + header.size);
}
const aligned_size = mem.alignForward(u64, file_size, self.page_size);
const page_size = MachO.getPageSize(macho_file.base.options.target.cpu.arch);
const aligned_size = mem.alignForward(u64, file_size, page_size);
dwarf_segment.vmaddr = base_vmaddr;
dwarf_segment.filesize = aligned_size;
dwarf_segment.vmsize = aligned_size;
@ -364,12 +342,12 @@ fn finalizeDwarfSegment(self: *DebugSymbols, macho_file: *MachO) void {
linkedit.vmaddr = mem.alignForward(
u64,
dwarf_segment.vmaddr + aligned_size,
self.page_size,
page_size,
);
linkedit.fileoff = mem.alignForward(
u64,
dwarf_segment.fileoff + aligned_size,
self.page_size,
page_size,
);
log.debug("found __LINKEDIT segment free space at 0x{x}", .{linkedit.fileoff});
}
@ -457,8 +435,9 @@ fn writeLinkeditSegmentData(self: *DebugSymbols, macho_file: *MachO) !void {
try self.writeSymtab(macho_file);
try self.writeStrtab();
const page_size = MachO.getPageSize(macho_file.base.options.target.cpu.arch);
const seg = &self.segments.items[self.linkedit_segment_cmd_index.?];
const aligned_size = mem.alignForward(u64, seg.filesize, self.page_size);
const aligned_size = mem.alignForward(u64, seg.filesize, page_size);
seg.vmsize = aligned_size;
}
@ -473,7 +452,7 @@ fn writeSymtab(self: *DebugSymbols, macho_file: *MachO) !void {
for (macho_file.locals.items, 0..) |sym, sym_id| {
if (sym.n_strx == 0) continue; // no name, skip
const sym_loc = MachO.SymbolWithLoc{ .sym_index = @as(u32, @intCast(sym_id)), .file = null };
const sym_loc = MachO.SymbolWithLoc{ .sym_index = @as(u32, @intCast(sym_id)) };
if (macho_file.symbolIsTemp(sym_loc)) continue; // local temp symbol, skip
if (macho_file.getGlobal(macho_file.getSymbolName(sym_loc)) != null) continue; // global symbol is either an export or import, skip
var out_sym = sym;
@ -567,3 +546,26 @@ pub fn getSection(self: DebugSymbols, sect: u8) macho.section_64 {
assert(sect < self.sections.items.len);
return self.sections.items[sect];
}
const DebugSymbols = @This();
const std = @import("std");
const build_options = @import("build_options");
const assert = std.debug.assert;
const fs = std.fs;
const link = @import("../../link.zig");
const load_commands = @import("load_commands.zig");
const log = std.log.scoped(.dsym);
const macho = std.macho;
const makeStaticString = MachO.makeStaticString;
const math = std.math;
const mem = std.mem;
const padToIdeal = MachO.padToIdeal;
const trace = @import("../../tracy.zig").trace;
const Allocator = mem.Allocator;
const Dwarf = @import("../Dwarf.zig");
const MachO = @import("../MachO.zig");
const Module = @import("../../Module.zig");
const StringTable = @import("../strtab.zig").StringTable;
const Type = @import("../../type.zig").Type;

View File

@ -1,17 +1,3 @@
const DwarfInfo = @This();
const std = @import("std");
const assert = std.debug.assert;
const dwarf = std.dwarf;
const leb = std.leb;
const log = std.log.scoped(.macho);
const math = std.math;
const mem = std.mem;
const Allocator = mem.Allocator;
pub const AbbrevLookupTable = std.AutoHashMap(u64, struct { pos: usize, len: usize });
pub const SubprogramLookupByName = std.StringHashMap(struct { addr: u64, size: u64 });
debug_info: []const u8,
debug_abbrev: []const u8,
debug_str: []const u8,
@ -458,7 +444,8 @@ fn findFormSize(self: DwarfInfo, form: u64, di_off: usize, cuh: CompileUnit.Head
},
else => {
log.err("unhandled DW_FORM_* value with identifier {x}", .{form});
// TODO figure out how to handle this
log.debug("unhandled DW_FORM_* value with identifier {x}", .{form});
return error.UnhandledDwFormValue;
},
}
@ -501,3 +488,17 @@ fn getString(self: DwarfInfo, off: u64) []const u8 {
assert(off < self.debug_str.len);
return mem.sliceTo(@as([*:0]const u8, @ptrCast(self.debug_str.ptr + @as(usize, @intCast(off)))), 0);
}
const DwarfInfo = @This();
const std = @import("std");
const assert = std.debug.assert;
const dwarf = std.dwarf;
const leb = std.leb;
const log = std.log.scoped(.macho);
const math = std.math;
const mem = std.mem;
const Allocator = mem.Allocator;
pub const AbbrevLookupTable = std.AutoHashMap(u64, struct { pos: usize, len: usize });
pub const SubprogramLookupByName = std.StringHashMap(struct { addr: u64, size: u64 });

View File

@ -1,25 +1,8 @@
const Dylib = @This();
const std = @import("std");
const assert = std.debug.assert;
const fs = std.fs;
const fmt = std.fmt;
const log = std.log.scoped(.link);
const macho = std.macho;
const math = std.math;
const mem = std.mem;
const fat = @import("fat.zig");
const tapi = @import("../tapi.zig");
const Allocator = mem.Allocator;
const CrossTarget = std.zig.CrossTarget;
const LibStub = tapi.LibStub;
const LoadCommandIterator = macho.LoadCommandIterator;
const MachO = @import("../MachO.zig");
const Tbd = tapi.Tbd;
path: []const u8,
id: ?Id = null,
weak: bool = false,
/// Header is only set if Dylib is parsed directly from a binary and not a stub file.
header: ?macho.mach_header_64 = null,
/// Parsed symbol table represented as hash map of symbols'
/// names. We can and should defer creating *Symbols until
@ -116,7 +99,15 @@ pub const Id = struct {
}
};
pub fn isDylib(file: std.fs.File, fat_offset: u64) bool {
const reader = file.reader();
const hdr = reader.readStruct(macho.mach_header_64) catch return false;
defer file.seekTo(fat_offset) catch {};
return hdr.filetype == macho.MH_DYLIB;
}
pub fn deinit(self: *Dylib, allocator: Allocator) void {
allocator.free(self.path);
for (self.symbols.keys()) |key| {
allocator.free(key);
}
@ -129,7 +120,6 @@ pub fn deinit(self: *Dylib, allocator: Allocator) void {
pub fn parseFromBinary(
self: *Dylib,
allocator: Allocator,
cpu_arch: std.Target.Cpu.Arch,
dylib_id: u16,
dependent_libs: anytype,
name: []const u8,
@ -140,27 +130,12 @@ pub fn parseFromBinary(
log.debug("parsing shared library '{s}'", .{name});
const header = try reader.readStruct(macho.mach_header_64);
self.header = try reader.readStruct(macho.mach_header_64);
if (header.filetype != macho.MH_DYLIB) {
log.debug("invalid filetype: expected 0x{x}, found 0x{x}", .{ macho.MH_DYLIB, header.filetype });
return error.NotDylib;
}
const this_arch: std.Target.Cpu.Arch = try fat.decodeArch(header.cputype, true);
if (this_arch != cpu_arch) {
log.err("mismatched cpu architecture: expected {s}, found {s}", .{
@tagName(cpu_arch),
@tagName(this_arch),
});
return error.MismatchedCpuArchitecture;
}
const should_lookup_reexports = header.flags & macho.MH_NO_REEXPORTED_DYLIBS == 0;
const should_lookup_reexports = self.header.?.flags & macho.MH_NO_REEXPORTED_DYLIBS == 0;
var it = LoadCommandIterator{
.ncmds = header.ncmds,
.buffer = data[@sizeOf(macho.mach_header_64)..][0..header.sizeofcmds],
.ncmds = self.header.?.ncmds,
.buffer = data[@sizeOf(macho.mach_header_64)..][0..self.header.?.sizeofcmds],
};
while (it.next()) |cmd| {
switch (cmd.cmd()) {
@ -205,6 +180,26 @@ pub fn parseFromBinary(
}
}
/// Returns Platform composed from the first encountered build version type load command:
/// either LC_BUILD_VERSION or LC_VERSION_MIN_*.
pub fn getPlatform(self: Dylib, data: []align(@alignOf(u64)) const u8) ?Platform {
var it = LoadCommandIterator{
.ncmds = self.header.?.ncmds,
.buffer = data[@sizeOf(macho.mach_header_64)..][0..self.header.?.sizeofcmds],
};
while (it.next()) |cmd| {
switch (cmd.cmd()) {
.BUILD_VERSION,
.VERSION_MIN_MACOSX,
.VERSION_MIN_IPHONEOS,
.VERSION_MIN_TVOS,
.VERSION_MIN_WATCHOS,
=> return Platform.fromLoadCommand(cmd),
else => {},
}
} else return null;
}
fn addObjCClassSymbol(self: *Dylib, allocator: Allocator, sym_name: []const u8) !void {
const expanded = &[_][]const u8{
try std.fmt.allocPrint(allocator, "_OBJC_CLASS_$_{s}", .{sym_name}),
@ -239,41 +234,41 @@ fn addWeakSymbol(self: *Dylib, allocator: Allocator, sym_name: []const u8) !void
try self.symbols.putNoClobber(allocator, try allocator.dupe(u8, sym_name), true);
}
const TargetMatcher = struct {
pub const TargetMatcher = struct {
allocator: Allocator,
target: CrossTarget,
cpu_arch: std.Target.Cpu.Arch,
os_tag: std.Target.Os.Tag,
abi: std.Target.Abi,
target_strings: std.ArrayListUnmanaged([]const u8) = .{},
fn init(allocator: Allocator, target: CrossTarget) !TargetMatcher {
pub fn init(allocator: Allocator, target: std.Target) !TargetMatcher {
var self = TargetMatcher{
.allocator = allocator,
.target = target,
.cpu_arch = target.cpu.arch,
.os_tag = target.os.tag,
.abi = target.abi,
};
const apple_string = try targetToAppleString(allocator, target);
const apple_string = try toAppleTargetTriple(allocator, self.cpu_arch, self.os_tag, self.abi);
try self.target_strings.append(allocator, apple_string);
const abi = target.abi orelse .none;
if (abi == .simulator) {
if (self.abi == .simulator) {
// For Apple simulator targets, linking gets tricky as we need to link against the simulator
// hosts dylibs too.
const host_target = try targetToAppleString(allocator, .{
.cpu_arch = target.cpu_arch.?,
.os_tag = .macos,
});
const host_target = try toAppleTargetTriple(allocator, self.cpu_arch, .macos, .none);
try self.target_strings.append(allocator, host_target);
}
return self;
}
fn deinit(self: *TargetMatcher) void {
pub fn deinit(self: *TargetMatcher) void {
for (self.target_strings.items) |t| {
self.allocator.free(t);
}
self.target_strings.deinit(self.allocator);
}
inline fn cpuArchToAppleString(cpu_arch: std.Target.Cpu.Arch) []const u8 {
inline fn fmtCpuArch(cpu_arch: std.Target.Cpu.Arch) []const u8 {
return switch (cpu_arch) {
.aarch64 => "arm64",
.x86_64 => "x86_64",
@ -281,7 +276,7 @@ const TargetMatcher = struct {
};
}
inline fn abiToAppleString(abi: std.Target.Abi) ?[]const u8 {
inline fn fmtAbi(abi: std.Target.Abi) ?[]const u8 {
return switch (abi) {
.none => null,
.simulator => "simulator",
@ -290,14 +285,18 @@ const TargetMatcher = struct {
};
}
fn targetToAppleString(allocator: Allocator, target: CrossTarget) ![]const u8 {
const cpu_arch = cpuArchToAppleString(target.cpu_arch.?);
const os_tag = @tagName(target.os_tag.?);
const target_abi = abiToAppleString(target.abi orelse .none);
if (target_abi) |abi| {
return std.fmt.allocPrint(allocator, "{s}-{s}-{s}", .{ cpu_arch, os_tag, abi });
pub fn toAppleTargetTriple(
allocator: Allocator,
cpu_arch: std.Target.Cpu.Arch,
os_tag: std.Target.Os.Tag,
abi: std.Target.Abi,
) ![]const u8 {
const cpu_arch_s = fmtCpuArch(cpu_arch);
const os_tag_s = @tagName(os_tag);
if (fmtAbi(abi)) |abi_s| {
return std.fmt.allocPrint(allocator, "{s}-{s}-{s}", .{ cpu_arch_s, os_tag_s, abi_s });
}
return std.fmt.allocPrint(allocator, "{s}-{s}", .{ cpu_arch, os_tag });
return std.fmt.allocPrint(allocator, "{s}-{s}", .{ cpu_arch_s, os_tag_s });
}
fn hasValue(stack: []const []const u8, needle: []const u8) bool {
@ -307,7 +306,7 @@ const TargetMatcher = struct {
return false;
}
fn matchesTarget(self: TargetMatcher, targets: []const []const u8) bool {
pub fn matchesTarget(self: TargetMatcher, targets: []const []const u8) bool {
for (self.target_strings.items) |t| {
if (hasValue(targets, t)) return true;
}
@ -315,26 +314,7 @@ const TargetMatcher = struct {
}
fn matchesArch(self: TargetMatcher, archs: []const []const u8) bool {
return hasValue(archs, cpuArchToAppleString(self.target.cpu_arch.?));
}
fn matchesTargetTbd(self: TargetMatcher, tbd: Tbd) !bool {
var arena = std.heap.ArenaAllocator.init(self.allocator);
defer arena.deinit();
const targets = switch (tbd) {
.v3 => |v3| blk: {
var targets = std.ArrayList([]const u8).init(arena.allocator());
for (v3.archs) |arch| {
const target = try std.fmt.allocPrint(arena.allocator(), "{s}-{s}", .{ arch, v3.platform });
try targets.append(target);
}
break :blk targets.items;
},
.v4 => |v4| v4.targets,
};
return self.matchesTarget(targets);
return hasValue(archs, fmtCpuArch(self.cpu_arch));
}
};
@ -347,7 +327,7 @@ pub fn parseFromStub(
dependent_libs: anytype,
name: []const u8,
) !void {
if (lib_stub.inner.len == 0) return error.EmptyStubFile;
if (lib_stub.inner.len == 0) return error.NotLibStub;
log.debug("parsing shared library from stub '{s}'", .{name});
@ -369,15 +349,16 @@ pub fn parseFromStub(
log.debug(" (install_name '{s}')", .{umbrella_lib.installName()});
var matcher = try TargetMatcher.init(allocator, .{
.cpu_arch = target.cpu.arch,
.os_tag = target.os.tag,
.abi = target.abi,
});
var matcher = try TargetMatcher.init(allocator, target);
defer matcher.deinit();
for (lib_stub.inner, 0..) |elem, stub_index| {
if (!(try matcher.matchesTargetTbd(elem))) continue;
const targets = try elem.targets(allocator);
defer {
for (targets) |t| allocator.free(t);
allocator.free(targets);
}
if (!matcher.matchesTarget(targets)) continue;
if (stub_index > 0) {
// TODO I thought that we could switch on presence of `parent-umbrella` map;
@ -553,3 +534,23 @@ pub fn parseFromStub(
}
}
}
const Dylib = @This();
const std = @import("std");
const assert = std.debug.assert;
const fs = std.fs;
const fmt = std.fmt;
const log = std.log.scoped(.link);
const macho = std.macho;
const math = std.math;
const mem = std.mem;
const fat = @import("fat.zig");
const tapi = @import("../tapi.zig");
const Allocator = mem.Allocator;
const LibStub = tapi.LibStub;
const LoadCommandIterator = macho.LoadCommandIterator;
const MachO = @import("../MachO.zig");
const Platform = @import("load_commands.zig").Platform;
const Tbd = tapi.Tbd;

View File

@ -2,31 +2,6 @@
//! Each Object is fully loaded into memory for easier
//! access into different data within.
const Object = @This();
const std = @import("std");
const build_options = @import("build_options");
const assert = std.debug.assert;
const dwarf = std.dwarf;
const eh_frame = @import("eh_frame.zig");
const fs = std.fs;
const io = std.io;
const log = std.log.scoped(.link);
const macho = std.macho;
const math = std.math;
const mem = std.mem;
const sort = std.sort;
const trace = @import("../../tracy.zig").trace;
const Allocator = mem.Allocator;
const Atom = @import("ZldAtom.zig");
const AtomIndex = @import("zld.zig").AtomIndex;
const DwarfInfo = @import("DwarfInfo.zig");
const LoadCommandIterator = macho.LoadCommandIterator;
const Zld = @import("zld.zig").Zld;
const SymbolWithLoc = @import("zld.zig").SymbolWithLoc;
const UnwindInfo = @import("UnwindInfo.zig");
name: []const u8,
mtime: u64,
contents: []align(@alignOf(u64)) const u8,
@ -54,7 +29,7 @@ source_section_index_lookup: []Entry = undefined,
/// Can be undefined as set together with in_symtab.
strtab_lookup: []u32 = undefined,
/// Can be undefined as set together with in_symtab.
atom_by_index_table: []AtomIndex = undefined,
atom_by_index_table: []?Atom.Index = undefined,
/// Can be undefined as set together with in_symtab.
globals_lookup: []i64 = undefined,
/// Can be undefined as set together with in_symtab.
@ -70,8 +45,8 @@ section_relocs_lookup: std.ArrayListUnmanaged(u32) = .{},
/// Data-in-code records sorted by address.
data_in_code: std.ArrayListUnmanaged(macho.data_in_code_entry) = .{},
atoms: std.ArrayListUnmanaged(AtomIndex) = .{},
exec_atoms: std.ArrayListUnmanaged(AtomIndex) = .{},
atoms: std.ArrayListUnmanaged(Atom.Index) = .{},
exec_atoms: std.ArrayListUnmanaged(Atom.Index) = .{},
eh_frame_sect_id: ?u8 = null,
eh_frame_relocs_lookup: std.AutoArrayHashMapUnmanaged(u32, Record) = .{},
@ -91,6 +66,13 @@ const Record = struct {
reloc: Entry,
};
pub fn isObject(file: std.fs.File) bool {
const reader = file.reader();
const hdr = reader.readStruct(macho.mach_header_64) catch return false;
defer file.seekTo(0) catch {};
return hdr.filetype == macho.MH_OBJECT;
}
pub fn deinit(self: *Object, gpa: Allocator) void {
self.atoms.deinit(gpa);
self.exec_atoms.deinit(gpa);
@ -118,36 +100,12 @@ pub fn deinit(self: *Object, gpa: Allocator) void {
self.data_in_code.deinit(gpa);
}
pub fn parse(self: *Object, allocator: Allocator, cpu_arch: std.Target.Cpu.Arch) !void {
pub fn parse(self: *Object, allocator: Allocator) !void {
var stream = std.io.fixedBufferStream(self.contents);
const reader = stream.reader();
self.header = try reader.readStruct(macho.mach_header_64);
if (self.header.filetype != macho.MH_OBJECT) {
log.debug("invalid filetype: expected 0x{x}, found 0x{x}", .{
macho.MH_OBJECT,
self.header.filetype,
});
return error.NotObject;
}
const this_arch: std.Target.Cpu.Arch = switch (self.header.cputype) {
macho.CPU_TYPE_ARM64 => .aarch64,
macho.CPU_TYPE_X86_64 => .x86_64,
else => |value| {
log.err("unsupported cpu architecture 0x{x}", .{value});
return error.UnsupportedCpuArchitecture;
},
};
if (this_arch != cpu_arch) {
log.err("mismatched cpu architecture: expected {s}, found {s}", .{
@tagName(cpu_arch),
@tagName(this_arch),
});
return error.MismatchedCpuArchitecture;
}
var it = LoadCommandIterator{
.ncmds = self.header.ncmds,
.buffer = self.contents[@sizeOf(macho.mach_header_64)..][0..self.header.sizeofcmds],
@ -172,7 +130,7 @@ pub fn parse(self: *Object, allocator: Allocator, cpu_arch: std.Target.Cpu.Arch)
self.reverse_symtab_lookup = try allocator.alloc(u32, self.in_symtab.?.len);
self.strtab_lookup = try allocator.alloc(u32, self.in_symtab.?.len);
self.globals_lookup = try allocator.alloc(i64, self.in_symtab.?.len);
self.atom_by_index_table = try allocator.alloc(AtomIndex, self.in_symtab.?.len + nsects);
self.atom_by_index_table = try allocator.alloc(?Atom.Index, self.in_symtab.?.len + nsects);
self.relocs_lookup = try allocator.alloc(Entry, self.in_symtab.?.len + nsects);
// This is wasteful but we need to be able to lookup source symbol address after stripping and
// allocating of sections.
@ -190,7 +148,7 @@ pub fn parse(self: *Object, allocator: Allocator, cpu_arch: std.Target.Cpu.Arch)
}
@memset(self.globals_lookup, -1);
@memset(self.atom_by_index_table, 0);
@memset(self.atom_by_index_table, null);
@memset(self.source_section_index_lookup, .{});
@memset(self.relocs_lookup, .{});
@ -331,10 +289,10 @@ fn filterSymbolsBySection(symbols: []macho.nlist_64, n_sect: u8) struct {
}
};
const index = @import("zld.zig").lsearch(macho.nlist_64, symbols, FirstMatch{
const index = MachO.lsearch(macho.nlist_64, symbols, FirstMatch{
.n_sect = n_sect,
});
const len = @import("zld.zig").lsearch(macho.nlist_64, symbols[index..], FirstNonMatch{
const len = MachO.lsearch(macho.nlist_64, symbols[index..], FirstNonMatch{
.n_sect = n_sect,
});
@ -353,10 +311,10 @@ fn filterSymbolsByAddress(symbols: []macho.nlist_64, start_addr: u64, end_addr:
}
};
const index = @import("zld.zig").lsearch(macho.nlist_64, symbols, Predicate{
const index = MachO.lsearch(macho.nlist_64, symbols, Predicate{
.addr = start_addr,
});
const len = @import("zld.zig").lsearch(macho.nlist_64, symbols[index..], Predicate{
const len = MachO.lsearch(macho.nlist_64, symbols[index..], Predicate{
.addr = end_addr,
});
@ -376,25 +334,32 @@ fn sectionLessThanByAddress(ctx: void, lhs: SortedSection, rhs: SortedSection) b
return lhs.header.addr < rhs.header.addr;
}
pub fn splitIntoAtoms(self: *Object, zld: *Zld, object_id: u32) !void {
pub const SplitIntoAtomsError = error{
OutOfMemory,
EndOfStream,
MissingEhFrameSection,
BadDwarfCfi,
};
pub fn splitIntoAtoms(self: *Object, macho_file: *MachO, object_id: u32) SplitIntoAtomsError!void {
log.debug("splitting object({d}, {s}) into atoms", .{ object_id, self.name });
try self.splitRegularSections(zld, object_id);
try self.parseEhFrameSection(zld, object_id);
try self.parseUnwindInfo(zld, object_id);
try self.parseDataInCode(zld.gpa);
try self.splitRegularSections(macho_file, object_id);
try self.parseEhFrameSection(macho_file, object_id);
try self.parseUnwindInfo(macho_file, object_id);
try self.parseDataInCode(macho_file.base.allocator);
}
/// Splits input regular sections into Atoms.
/// If the Object was compiled with `MH_SUBSECTIONS_VIA_SYMBOLS`, splits section
/// into subsections where each subsection then represents an Atom.
pub fn splitRegularSections(self: *Object, zld: *Zld, object_id: u32) !void {
const gpa = zld.gpa;
pub fn splitRegularSections(self: *Object, macho_file: *MachO, object_id: u32) !void {
const gpa = macho_file.base.allocator;
const sections = self.getSourceSections();
for (sections, 0..) |sect, id| {
if (sect.isDebug()) continue;
const out_sect_id = (try zld.getOutputSection(sect)) orelse {
const out_sect_id = (try Atom.getOutputSection(macho_file, sect)) orelse {
log.debug(" unhandled section '{s},{s}'", .{ sect.segName(), sect.sectName() });
continue;
};
@ -414,13 +379,13 @@ pub fn splitRegularSections(self: *Object, zld: *Zld, object_id: u32) !void {
if (self.in_symtab == null) {
for (sections, 0..) |sect, id| {
if (sect.isDebug()) continue;
const out_sect_id = (try zld.getOutputSection(sect)) orelse continue;
const out_sect_id = (try Atom.getOutputSection(macho_file, sect)) orelse continue;
if (sect.size == 0) continue;
const sect_id = @as(u8, @intCast(id));
const sym_index = self.getSectionAliasSymbolIndex(sect_id);
const atom_index = try self.createAtomFromSubsection(
zld,
macho_file,
object_id,
sym_index,
sym_index,
@ -429,7 +394,7 @@ pub fn splitRegularSections(self: *Object, zld: *Zld, object_id: u32) !void {
sect.@"align",
out_sect_id,
);
zld.addAtomToSection(atom_index);
macho_file.addAtomToSection(atom_index);
}
return;
}
@ -437,7 +402,7 @@ pub fn splitRegularSections(self: *Object, zld: *Zld, object_id: u32) !void {
// Well, shit, sometimes compilers skip the dysymtab load command altogether, meaning we
// have to infer the start of undef section in the symtab ourselves.
const iundefsym = blk: {
const dysymtab = self.parseDysymtab() orelse {
const dysymtab = self.getDysymtab() orelse {
var iundefsym: usize = self.in_symtab.?.len;
while (iundefsym > 0) : (iundefsym -= 1) {
const sym = self.symtab[iundefsym - 1];
@ -473,17 +438,17 @@ pub fn splitRegularSections(self: *Object, zld: *Zld, object_id: u32) !void {
log.debug("splitting section '{s},{s}' into atoms", .{ sect.segName(), sect.sectName() });
// Get output segment/section in the final artifact.
const out_sect_id = (try zld.getOutputSection(sect)) orelse continue;
const out_sect_id = (try Atom.getOutputSection(macho_file, sect)) orelse continue;
log.debug(" output sect({d}, '{s},{s}')", .{
out_sect_id + 1,
zld.sections.items(.header)[out_sect_id].segName(),
zld.sections.items(.header)[out_sect_id].sectName(),
macho_file.sections.items(.header)[out_sect_id].segName(),
macho_file.sections.items(.header)[out_sect_id].sectName(),
});
try self.parseRelocs(gpa, section.id);
const cpu_arch = zld.options.target.cpu.arch;
const cpu_arch = macho_file.base.options.target.cpu.arch;
const sect_loc = filterSymbolsBySection(symtab[sect_sym_index..], sect_id + 1);
const sect_start_index = sect_sym_index + sect_loc.index;
@ -499,7 +464,7 @@ pub fn splitRegularSections(self: *Object, zld: *Zld, object_id: u32) !void {
const sym_index = self.getSectionAliasSymbolIndex(sect_id);
const atom_size = first_sym.n_value - sect.addr;
const atom_index = try self.createAtomFromSubsection(
zld,
macho_file,
object_id,
sym_index,
sym_index,
@ -509,9 +474,9 @@ pub fn splitRegularSections(self: *Object, zld: *Zld, object_id: u32) !void {
out_sect_id,
);
if (!sect.isZerofill()) {
try self.cacheRelocs(zld, atom_index);
try self.cacheRelocs(macho_file, atom_index);
}
zld.addAtomToSection(atom_index);
macho_file.addAtomToSection(atom_index);
}
var next_sym_index = sect_start_index;
@ -535,7 +500,7 @@ pub fn splitRegularSections(self: *Object, zld: *Zld, object_id: u32) !void {
sect.@"align";
const atom_index = try self.createAtomFromSubsection(
zld,
macho_file,
object_id,
atom_sym_index,
atom_sym_index,
@ -554,14 +519,14 @@ pub fn splitRegularSections(self: *Object, zld: *Zld, object_id: u32) !void {
self.atom_by_index_table[alias_index] = atom_index;
}
if (!sect.isZerofill()) {
try self.cacheRelocs(zld, atom_index);
try self.cacheRelocs(macho_file, atom_index);
}
zld.addAtomToSection(atom_index);
macho_file.addAtomToSection(atom_index);
}
} else {
const alias_index = self.getSectionAliasSymbolIndex(sect_id);
const atom_index = try self.createAtomFromSubsection(
zld,
macho_file,
object_id,
alias_index,
sect_start_index,
@ -571,16 +536,16 @@ pub fn splitRegularSections(self: *Object, zld: *Zld, object_id: u32) !void {
out_sect_id,
);
if (!sect.isZerofill()) {
try self.cacheRelocs(zld, atom_index);
try self.cacheRelocs(macho_file, atom_index);
}
zld.addAtomToSection(atom_index);
macho_file.addAtomToSection(atom_index);
}
}
}
fn createAtomFromSubsection(
self: *Object,
zld: *Zld,
macho_file: *MachO,
object_id: u32,
sym_index: u32,
inner_sym_index: u32,
@ -588,10 +553,10 @@ fn createAtomFromSubsection(
size: u64,
alignment: u32,
out_sect_id: u8,
) !AtomIndex {
const gpa = zld.gpa;
const atom_index = try zld.createEmptyAtom(sym_index, size, alignment);
const atom = zld.getAtomPtr(atom_index);
) !Atom.Index {
const gpa = macho_file.base.allocator;
const atom_index = try macho_file.createAtom(sym_index, .{ .size = size, .alignment = alignment });
const atom = macho_file.getAtomPtr(atom_index);
atom.inner_sym_index = inner_sym_index;
atom.inner_nsyms_trailing = inner_nsyms_trailing;
atom.file = object_id + 1;
@ -601,22 +566,22 @@ fn createAtomFromSubsection(
sym_index,
self.getSymbolName(sym_index),
out_sect_id + 1,
zld.sections.items(.header)[out_sect_id].segName(),
zld.sections.items(.header)[out_sect_id].sectName(),
macho_file.sections.items(.header)[out_sect_id].segName(),
macho_file.sections.items(.header)[out_sect_id].sectName(),
object_id,
});
try self.atoms.append(gpa, atom_index);
self.atom_by_index_table[sym_index] = atom_index;
var it = Atom.getInnerSymbolsIterator(zld, atom_index);
var it = Atom.getInnerSymbolsIterator(macho_file, atom_index);
while (it.next()) |sym_loc| {
const inner = zld.getSymbolPtr(sym_loc);
const inner = macho_file.getSymbolPtr(sym_loc);
inner.n_sect = out_sect_id + 1;
self.atom_by_index_table[sym_loc.sym_index] = atom_index;
}
const out_sect = zld.sections.items(.header)[out_sect_id];
const out_sect = macho_file.sections.items(.header)[out_sect_id];
if (out_sect.isCode() and
mem.eql(u8, "__TEXT", out_sect.segName()) and
mem.eql(u8, "__text", out_sect.sectName()))
@ -648,8 +613,8 @@ fn filterRelocs(
}
};
const start = @import("zld.zig").bsearch(macho.relocation_info, relocs, Predicate{ .addr = end_addr });
const len = @import("zld.zig").lsearch(macho.relocation_info, relocs[start..], LPredicate{ .addr = start_addr });
const start = MachO.bsearch(macho.relocation_info, relocs, Predicate{ .addr = end_addr });
const len = MachO.lsearch(macho.relocation_info, relocs[start..], LPredicate{ .addr = start_addr });
return .{ .start = @as(u32, @intCast(start)), .len = @as(u32, @intCast(len)) };
}
@ -668,8 +633,8 @@ fn parseRelocs(self: *Object, gpa: Allocator, sect_id: u8) !void {
self.section_relocs_lookup.items[sect_id] = start;
}
fn cacheRelocs(self: *Object, zld: *Zld, atom_index: AtomIndex) !void {
const atom = zld.getAtom(atom_index);
fn cacheRelocs(self: *Object, macho_file: *MachO, atom_index: Atom.Index) !void {
const atom = macho_file.getAtom(atom_index);
const source_sect_id = if (self.getSourceSymbol(atom.sym_index)) |source_sym| blk: {
break :blk source_sym.n_sect - 1;
@ -696,18 +661,19 @@ fn relocGreaterThan(ctx: void, lhs: macho.relocation_info, rhs: macho.relocation
return lhs.r_address > rhs.r_address;
}
fn parseEhFrameSection(self: *Object, zld: *Zld, object_id: u32) !void {
fn parseEhFrameSection(self: *Object, macho_file: *MachO, object_id: u32) !void {
const sect_id = self.eh_frame_sect_id orelse return;
const sect = self.getSourceSection(sect_id);
log.debug("parsing __TEXT,__eh_frame section", .{});
if (zld.getSectionByName("__TEXT", "__eh_frame") == null) {
_ = try zld.initSection("__TEXT", "__eh_frame", .{});
const gpa = macho_file.base.allocator;
if (macho_file.eh_frame_section_index == null) {
macho_file.eh_frame_section_index = try macho_file.initSection("__TEXT", "__eh_frame", .{});
}
const gpa = zld.gpa;
const cpu_arch = zld.options.target.cpu.arch;
const cpu_arch = macho_file.base.options.target.cpu.arch;
try self.parseRelocs(gpa, sect_id);
const relocs = self.getRelocs(sect_id);
@ -745,7 +711,7 @@ fn parseEhFrameSection(self: *Object, zld: *Zld, object_id: u32) !void {
@as(macho.reloc_type_arm64, @enumFromInt(rel.r_type)) == .ARM64_RELOC_UNSIGNED)
break rel;
} else unreachable;
const target = Atom.parseRelocTarget(zld, .{
const target = Atom.parseRelocTarget(macho_file, .{
.object_id = object_id,
.rel = rel,
.code = it.data[offset..],
@ -760,7 +726,7 @@ fn parseEhFrameSection(self: *Object, zld: *Zld, object_id: u32) !void {
});
const target_sym_index = self.getSymbolByAddress(target_address, null);
const target = if (self.getGlobal(target_sym_index)) |global_index|
zld.globals.items[global_index]
macho_file.globals.items[global_index]
else
SymbolWithLoc{ .sym_index = target_sym_index, .file = object_id + 1 };
break :blk target;
@ -786,7 +752,7 @@ fn parseEhFrameSection(self: *Object, zld: *Zld, object_id: u32) !void {
};
log.debug("FDE at offset {x} tracks {s}", .{
offset,
zld.getSymbolName(actual_target),
macho_file.getSymbolName(actual_target),
});
try self.eh_frame_records_lookup.putNoClobber(gpa, actual_target, offset);
}
@ -795,15 +761,21 @@ fn parseEhFrameSection(self: *Object, zld: *Zld, object_id: u32) !void {
}
}
fn parseUnwindInfo(self: *Object, zld: *Zld, object_id: u32) !void {
fn parseUnwindInfo(self: *Object, macho_file: *MachO, object_id: u32) !void {
const gpa = macho_file.base.allocator;
const cpu_arch = macho_file.base.options.target.cpu.arch;
const sect_id = self.unwind_info_sect_id orelse {
// If it so happens that the object had `__eh_frame` section defined but no `__compact_unwind`,
// we will try fully synthesising unwind info records to somewhat match Apple ld's
// approach. However, we will only synthesise DWARF records and nothing more. For this reason,
// we still create the output `__TEXT,__unwind_info` section.
if (self.hasEhFrameRecords()) {
if (zld.getSectionByName("__TEXT", "__unwind_info") == null) {
_ = try zld.initSection("__TEXT", "__unwind_info", .{});
if (macho_file.unwind_info_section_index == null) {
macho_file.unwind_info_section_index = try macho_file.initSection(
"__TEXT",
"__unwind_info",
.{},
);
}
}
return;
@ -811,11 +783,8 @@ fn parseUnwindInfo(self: *Object, zld: *Zld, object_id: u32) !void {
log.debug("parsing unwind info in {s}", .{self.name});
const gpa = zld.gpa;
const cpu_arch = zld.options.target.cpu.arch;
if (zld.getSectionByName("__TEXT", "__unwind_info") == null) {
_ = try zld.initSection("__TEXT", "__unwind_info", .{});
if (macho_file.unwind_info_section_index == null) {
macho_file.unwind_info_section_index = try macho_file.initSection("__TEXT", "__unwind_info", .{});
}
const unwind_records = self.getUnwindRecords();
@ -826,11 +795,7 @@ fn parseUnwindInfo(self: *Object, zld: *Zld, object_id: u32) !void {
if (UnwindInfo.UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) break true;
} else false;
if (needs_eh_frame and !self.hasEhFrameRecords()) {
log.err("missing __TEXT,__eh_frame section", .{});
log.err(" in object {s}", .{self.name});
return error.MissingSection;
}
if (needs_eh_frame and !self.hasEhFrameRecords()) return error.MissingEhFrameSection;
try self.parseRelocs(gpa, sect_id);
const relocs = self.getRelocs(sect_id);
@ -850,7 +815,7 @@ fn parseUnwindInfo(self: *Object, zld: *Zld, object_id: u32) !void {
// Find function symbol that this record describes
const rel = relocs[rel_pos.start..][rel_pos.len - 1];
const target = Atom.parseRelocTarget(zld, .{
const target = Atom.parseRelocTarget(macho_file, .{
.object_id = object_id,
.rel = rel,
.code = mem.asBytes(&record),
@ -874,7 +839,7 @@ fn parseUnwindInfo(self: *Object, zld: *Zld, object_id: u32) !void {
};
log.debug("unwind record {d} tracks {s}", .{
record_id,
zld.getSymbolName(actual_target),
macho_file.getSymbolName(actual_target),
});
try self.unwind_records_lookup.putNoClobber(gpa, actual_target, @intCast(record_id));
}
@ -945,16 +910,14 @@ fn diceLessThan(ctx: void, lhs: macho.data_in_code_entry, rhs: macho.data_in_cod
return lhs.offset < rhs.offset;
}
fn parseDysymtab(self: Object) ?macho.dysymtab_command {
fn getDysymtab(self: Object) ?macho.dysymtab_command {
var it = LoadCommandIterator{
.ncmds = self.header.ncmds,
.buffer = self.contents[@sizeOf(macho.mach_header_64)..][0..self.header.sizeofcmds],
};
while (it.next()) |cmd| {
switch (cmd.cmd()) {
.DYSYMTAB => {
return cmd.cast(macho.dysymtab_command).?;
},
.DYSYMTAB => return cmd.cast(macho.dysymtab_command).?,
else => {},
}
} else return null;
@ -980,6 +943,26 @@ pub fn parseDwarfInfo(self: Object) DwarfInfo {
return di;
}
/// Returns Platform composed from the first encountered build version type load command:
/// either LC_BUILD_VERSION or LC_VERSION_MIN_*.
pub fn getPlatform(self: Object) ?Platform {
var it = LoadCommandIterator{
.ncmds = self.header.ncmds,
.buffer = self.contents[@sizeOf(macho.mach_header_64)..][0..self.header.sizeofcmds],
};
while (it.next()) |cmd| {
switch (cmd.cmd()) {
.BUILD_VERSION,
.VERSION_MIN_MACOSX,
.VERSION_MIN_IPHONEOS,
.VERSION_MIN_TVOS,
.VERSION_MIN_WATCHOS,
=> return Platform.fromLoadCommand(cmd),
else => {},
}
} else return null;
}
pub fn getSectionContents(self: Object, sect: macho.section_64) []const u8 {
const size = @as(usize, @intCast(sect.size));
return self.contents[sect.offset..][0..size];
@ -1050,7 +1033,7 @@ pub fn getSymbolByAddress(self: Object, addr: u64, sect_hint: ?u8) u32 {
if (sect_hint) |sect_id| {
if (self.source_section_index_lookup[sect_id].len > 0) {
const lookup = self.source_section_index_lookup[sect_id];
const target_sym_index = @import("zld.zig").lsearch(
const target_sym_index = MachO.lsearch(
i64,
self.source_address_lookup[lookup.start..][0..lookup.len],
Predicate{ .addr = @as(i64, @intCast(addr)) },
@ -1065,7 +1048,7 @@ pub fn getSymbolByAddress(self: Object, addr: u64, sect_hint: ?u8) u32 {
return self.getSectionAliasSymbolIndex(sect_id);
}
const target_sym_index = @import("zld.zig").lsearch(i64, self.source_address_lookup, Predicate{
const target_sym_index = MachO.lsearch(i64, self.source_address_lookup, Predicate{
.addr = @as(i64, @intCast(addr)),
});
assert(target_sym_index > 0);
@ -1077,10 +1060,8 @@ pub fn getGlobal(self: Object, sym_index: u32) ?u32 {
return @as(u32, @intCast(self.globals_lookup[sym_index]));
}
pub fn getAtomIndexForSymbol(self: Object, sym_index: u32) ?AtomIndex {
const atom_index = self.atom_by_index_table[sym_index];
if (atom_index == 0) return null;
return atom_index;
pub fn getAtomIndexForSymbol(self: Object, sym_index: u32) ?Atom.Index {
return self.atom_by_index_table[sym_index];
}
pub fn hasUnwindRecords(self: Object) bool {
@ -1109,3 +1090,28 @@ pub fn getEhFrameRecordsIterator(self: Object) eh_frame.Iterator {
pub fn hasDataInCode(self: Object) bool {
return self.data_in_code.items.len > 0;
}
const Object = @This();
const std = @import("std");
const build_options = @import("build_options");
const assert = std.debug.assert;
const dwarf = std.dwarf;
const eh_frame = @import("eh_frame.zig");
const fs = std.fs;
const io = std.io;
const log = std.log.scoped(.link);
const macho = std.macho;
const math = std.math;
const mem = std.mem;
const sort = std.sort;
const trace = @import("../../tracy.zig").trace;
const Allocator = mem.Allocator;
const Atom = @import("Atom.zig");
const DwarfInfo = @import("DwarfInfo.zig");
const LoadCommandIterator = macho.LoadCommandIterator;
const MachO = @import("../MachO.zig");
const Platform = @import("load_commands.zig").Platform;
const SymbolWithLoc = MachO.SymbolWithLoc;
const UnwindInfo = @import("UnwindInfo.zig");

View File

@ -62,7 +62,7 @@ pub fn getTargetBaseAddress(self: Relocation, macho_file: *MachO) ?u64 {
const index = macho_file.stub_table.lookup.get(self.target) orelse return null;
const header = macho_file.sections.items(.header)[macho_file.stubs_section_index.?];
return header.addr +
index * @import("stubs.zig").calcStubEntrySize(macho_file.base.options.target.cpu.arch);
index * @import("stubs.zig").stubSize(macho_file.base.options.target.cpu.arch);
}
switch (self.type) {
.got, .got_page, .got_pageoff => {

View File

@ -28,248 +28,6 @@
//! 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
//! the addition chars in the symbol, followed by a uleb128 offset for the node that edge points to.
const Trie = @This();
const std = @import("std");
const mem = std.mem;
const leb = std.leb;
const log = std.log.scoped(.link);
const macho = std.macho;
const testing = std.testing;
const assert = std.debug.assert;
const Allocator = mem.Allocator;
pub const Node = struct {
base: *Trie,
/// Terminal info associated with this node.
/// If this node is not a terminal node, info is null.
terminal_info: ?struct {
/// Export flags associated with this exported symbol.
export_flags: u64,
/// VM address offset wrt to the section this symbol is defined against.
vmaddr_offset: u64,
} = null,
/// Offset of this node in the trie output byte stream.
trie_offset: ?u64 = null,
/// List of all edges originating from this node.
edges: std.ArrayListUnmanaged(Edge) = .{},
node_dirty: bool = true,
/// Edge connecting to nodes in the trie.
pub const Edge = struct {
from: *Node,
to: *Node,
label: []u8,
fn deinit(self: *Edge, allocator: Allocator) void {
self.to.deinit(allocator);
allocator.destroy(self.to);
allocator.free(self.label);
self.from = undefined;
self.to = undefined;
self.label = undefined;
}
};
fn deinit(self: *Node, allocator: Allocator) void {
for (self.edges.items) |*edge| {
edge.deinit(allocator);
}
self.edges.deinit(allocator);
}
/// Inserts a new node starting from `self`.
fn put(self: *Node, allocator: Allocator, label: []const u8) !*Node {
// Check for match with edges from this node.
for (self.edges.items) |*edge| {
const match = mem.indexOfDiff(u8, edge.label, label) orelse return edge.to;
if (match == 0) continue;
if (match == edge.label.len) return edge.to.put(allocator, label[match..]);
// Found a match, need to splice up nodes.
// From: A -> B
// To: A -> C -> B
const mid = try allocator.create(Node);
mid.* = .{ .base = self.base };
var to_label = try allocator.dupe(u8, edge.label[match..]);
allocator.free(edge.label);
const to_node = edge.to;
edge.to = mid;
edge.label = try allocator.dupe(u8, label[0..match]);
self.base.node_count += 1;
try mid.edges.append(allocator, .{
.from = mid,
.to = to_node,
.label = to_label,
});
return if (match == label.len) mid else mid.put(allocator, label[match..]);
}
// Add a new node.
const node = try allocator.create(Node);
node.* = .{ .base = self.base };
self.base.node_count += 1;
try self.edges.append(allocator, .{
.from = self,
.to = node,
.label = try allocator.dupe(u8, label),
});
return node;
}
/// Recursively parses the node from the input byte stream.
fn read(self: *Node, allocator: Allocator, reader: anytype) Trie.ReadError!usize {
self.node_dirty = true;
const trie_offset = try reader.context.getPos();
self.trie_offset = trie_offset;
var nread: usize = 0;
const node_size = try leb.readULEB128(u64, reader);
if (node_size > 0) {
const export_flags = try leb.readULEB128(u64, reader);
// TODO Parse special flags.
assert(export_flags & macho.EXPORT_SYMBOL_FLAGS_REEXPORT == 0 and
export_flags & macho.EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER == 0);
const vmaddr_offset = try leb.readULEB128(u64, reader);
self.terminal_info = .{
.export_flags = export_flags,
.vmaddr_offset = vmaddr_offset,
};
}
const nedges = try reader.readByte();
self.base.node_count += nedges;
nread += (try reader.context.getPos()) - trie_offset;
var i: usize = 0;
while (i < nedges) : (i += 1) {
const edge_start_pos = try reader.context.getPos();
const label = blk: {
var label_buf = std.ArrayList(u8).init(allocator);
while (true) {
const next = try reader.readByte();
if (next == @as(u8, 0))
break;
try label_buf.append(next);
}
break :blk try label_buf.toOwnedSlice();
};
const seek_to = try leb.readULEB128(u64, reader);
const return_pos = try reader.context.getPos();
nread += return_pos - edge_start_pos;
try reader.context.seekTo(seek_to);
const node = try allocator.create(Node);
node.* = .{ .base = self.base };
nread += try node.read(allocator, reader);
try self.edges.append(allocator, .{
.from = self,
.to = node,
.label = label,
});
try reader.context.seekTo(return_pos);
}
return nread;
}
/// Writes this node to a byte stream.
/// The children of this node *are* not written to the byte stream
/// recursively. To write all nodes to a byte stream in sequence,
/// iterate over `Trie.ordered_nodes` and call this method on each node.
/// This is one of the requirements of the MachO.
/// Panics if `finalize` was not called before calling this method.
fn write(self: Node, writer: anytype) !void {
assert(!self.node_dirty);
if (self.terminal_info) |info| {
// Terminal node info: encode export flags and vmaddr offset of this symbol.
var info_buf: [@sizeOf(u64) * 2]u8 = undefined;
var info_stream = std.io.fixedBufferStream(&info_buf);
// TODO Implement for special flags.
assert(info.export_flags & macho.EXPORT_SYMBOL_FLAGS_REEXPORT == 0 and
info.export_flags & macho.EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER == 0);
try leb.writeULEB128(info_stream.writer(), info.export_flags);
try leb.writeULEB128(info_stream.writer(), info.vmaddr_offset);
// Encode the size of the terminal node info.
var size_buf: [@sizeOf(u64)]u8 = undefined;
var size_stream = std.io.fixedBufferStream(&size_buf);
try leb.writeULEB128(size_stream.writer(), info_stream.pos);
// Now, write them to the output stream.
try writer.writeAll(size_buf[0..size_stream.pos]);
try writer.writeAll(info_buf[0..info_stream.pos]);
} else {
// Non-terminal node is delimited by 0 byte.
try writer.writeByte(0);
}
// Write number of edges (max legal number of edges is 256).
try writer.writeByte(@as(u8, @intCast(self.edges.items.len)));
for (self.edges.items) |edge| {
// Write edge label and offset to next node in trie.
try writer.writeAll(edge.label);
try writer.writeByte(0);
try leb.writeULEB128(writer, edge.to.trie_offset.?);
}
}
const FinalizeResult = struct {
/// Current size of this node in bytes.
node_size: u64,
/// True if the trie offset of this node in the output byte stream
/// would need updating; false otherwise.
updated: bool,
};
/// Updates offset of this node in the output byte stream.
fn finalize(self: *Node, offset_in_trie: u64) !FinalizeResult {
var stream = std.io.countingWriter(std.io.null_writer);
var writer = stream.writer();
var node_size: u64 = 0;
if (self.terminal_info) |info| {
try leb.writeULEB128(writer, info.export_flags);
try leb.writeULEB128(writer, info.vmaddr_offset);
try leb.writeULEB128(writer, stream.bytes_written);
} else {
node_size += 1; // 0x0 for non-terminal nodes
}
node_size += 1; // 1 byte for edge count
for (self.edges.items) |edge| {
const next_node_offset = edge.to.trie_offset orelse 0;
node_size += edge.label.len + 1;
try leb.writeULEB128(writer, next_node_offset);
}
const trie_offset = self.trie_offset orelse 0;
const updated = offset_in_trie != trie_offset;
self.trie_offset = offset_in_trie;
self.node_dirty = false;
node_size += stream.bytes_written;
return FinalizeResult{ .node_size = node_size, .updated = updated };
}
};
/// The root node of the trie.
root: ?*Node = null,
@ -611,3 +369,245 @@ test "ordering bug" {
_ = try trie.write(stream.writer());
try expectEqualHexStrings(&exp_buffer, buffer);
}
pub const Node = struct {
base: *Trie,
/// Terminal info associated with this node.
/// If this node is not a terminal node, info is null.
terminal_info: ?struct {
/// Export flags associated with this exported symbol.
export_flags: u64,
/// VM address offset wrt to the section this symbol is defined against.
vmaddr_offset: u64,
} = null,
/// Offset of this node in the trie output byte stream.
trie_offset: ?u64 = null,
/// List of all edges originating from this node.
edges: std.ArrayListUnmanaged(Edge) = .{},
node_dirty: bool = true,
/// Edge connecting to nodes in the trie.
pub const Edge = struct {
from: *Node,
to: *Node,
label: []u8,
fn deinit(self: *Edge, allocator: Allocator) void {
self.to.deinit(allocator);
allocator.destroy(self.to);
allocator.free(self.label);
self.from = undefined;
self.to = undefined;
self.label = undefined;
}
};
fn deinit(self: *Node, allocator: Allocator) void {
for (self.edges.items) |*edge| {
edge.deinit(allocator);
}
self.edges.deinit(allocator);
}
/// Inserts a new node starting from `self`.
fn put(self: *Node, allocator: Allocator, label: []const u8) !*Node {
// Check for match with edges from this node.
for (self.edges.items) |*edge| {
const match = mem.indexOfDiff(u8, edge.label, label) orelse return edge.to;
if (match == 0) continue;
if (match == edge.label.len) return edge.to.put(allocator, label[match..]);
// Found a match, need to splice up nodes.
// From: A -> B
// To: A -> C -> B
const mid = try allocator.create(Node);
mid.* = .{ .base = self.base };
var to_label = try allocator.dupe(u8, edge.label[match..]);
allocator.free(edge.label);
const to_node = edge.to;
edge.to = mid;
edge.label = try allocator.dupe(u8, label[0..match]);
self.base.node_count += 1;
try mid.edges.append(allocator, .{
.from = mid,
.to = to_node,
.label = to_label,
});
return if (match == label.len) mid else mid.put(allocator, label[match..]);
}
// Add a new node.
const node = try allocator.create(Node);
node.* = .{ .base = self.base };
self.base.node_count += 1;
try self.edges.append(allocator, .{
.from = self,
.to = node,
.label = try allocator.dupe(u8, label),
});
return node;
}
/// Recursively parses the node from the input byte stream.
fn read(self: *Node, allocator: Allocator, reader: anytype) Trie.ReadError!usize {
self.node_dirty = true;
const trie_offset = try reader.context.getPos();
self.trie_offset = trie_offset;
var nread: usize = 0;
const node_size = try leb.readULEB128(u64, reader);
if (node_size > 0) {
const export_flags = try leb.readULEB128(u64, reader);
// TODO Parse special flags.
assert(export_flags & macho.EXPORT_SYMBOL_FLAGS_REEXPORT == 0 and
export_flags & macho.EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER == 0);
const vmaddr_offset = try leb.readULEB128(u64, reader);
self.terminal_info = .{
.export_flags = export_flags,
.vmaddr_offset = vmaddr_offset,
};
}
const nedges = try reader.readByte();
self.base.node_count += nedges;
nread += (try reader.context.getPos()) - trie_offset;
var i: usize = 0;
while (i < nedges) : (i += 1) {
const edge_start_pos = try reader.context.getPos();
const label = blk: {
var label_buf = std.ArrayList(u8).init(allocator);
while (true) {
const next = try reader.readByte();
if (next == @as(u8, 0))
break;
try label_buf.append(next);
}
break :blk try label_buf.toOwnedSlice();
};
const seek_to = try leb.readULEB128(u64, reader);
const return_pos = try reader.context.getPos();
nread += return_pos - edge_start_pos;
try reader.context.seekTo(seek_to);
const node = try allocator.create(Node);
node.* = .{ .base = self.base };
nread += try node.read(allocator, reader);
try self.edges.append(allocator, .{
.from = self,
.to = node,
.label = label,
});
try reader.context.seekTo(return_pos);
}
return nread;
}
/// Writes this node to a byte stream.
/// The children of this node *are* not written to the byte stream
/// recursively. To write all nodes to a byte stream in sequence,
/// iterate over `Trie.ordered_nodes` and call this method on each node.
/// This is one of the requirements of the MachO.
/// Panics if `finalize` was not called before calling this method.
fn write(self: Node, writer: anytype) !void {
assert(!self.node_dirty);
if (self.terminal_info) |info| {
// Terminal node info: encode export flags and vmaddr offset of this symbol.
var info_buf: [@sizeOf(u64) * 2]u8 = undefined;
var info_stream = std.io.fixedBufferStream(&info_buf);
// TODO Implement for special flags.
assert(info.export_flags & macho.EXPORT_SYMBOL_FLAGS_REEXPORT == 0 and
info.export_flags & macho.EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER == 0);
try leb.writeULEB128(info_stream.writer(), info.export_flags);
try leb.writeULEB128(info_stream.writer(), info.vmaddr_offset);
// Encode the size of the terminal node info.
var size_buf: [@sizeOf(u64)]u8 = undefined;
var size_stream = std.io.fixedBufferStream(&size_buf);
try leb.writeULEB128(size_stream.writer(), info_stream.pos);
// Now, write them to the output stream.
try writer.writeAll(size_buf[0..size_stream.pos]);
try writer.writeAll(info_buf[0..info_stream.pos]);
} else {
// Non-terminal node is delimited by 0 byte.
try writer.writeByte(0);
}
// Write number of edges (max legal number of edges is 256).
try writer.writeByte(@as(u8, @intCast(self.edges.items.len)));
for (self.edges.items) |edge| {
// Write edge label and offset to next node in trie.
try writer.writeAll(edge.label);
try writer.writeByte(0);
try leb.writeULEB128(writer, edge.to.trie_offset.?);
}
}
const FinalizeResult = struct {
/// Current size of this node in bytes.
node_size: u64,
/// True if the trie offset of this node in the output byte stream
/// would need updating; false otherwise.
updated: bool,
};
/// Updates offset of this node in the output byte stream.
fn finalize(self: *Node, offset_in_trie: u64) !FinalizeResult {
var stream = std.io.countingWriter(std.io.null_writer);
var writer = stream.writer();
var node_size: u64 = 0;
if (self.terminal_info) |info| {
try leb.writeULEB128(writer, info.export_flags);
try leb.writeULEB128(writer, info.vmaddr_offset);
try leb.writeULEB128(writer, stream.bytes_written);
} else {
node_size += 1; // 0x0 for non-terminal nodes
}
node_size += 1; // 1 byte for edge count
for (self.edges.items) |edge| {
const next_node_offset = edge.to.trie_offset orelse 0;
node_size += edge.label.len + 1;
try leb.writeULEB128(writer, next_node_offset);
}
const trie_offset = self.trie_offset orelse 0;
const updated = offset_in_trie != trie_offset;
self.trie_offset = offset_in_trie;
self.node_dirty = false;
node_size += stream.bytes_written;
return FinalizeResult{ .node_size = node_size, .updated = updated };
}
};
const Trie = @This();
const std = @import("std");
const mem = std.mem;
const leb = std.leb;
const log = std.log.scoped(.link);
const macho = std.macho;
const testing = std.testing;
const assert = std.debug.assert;
const Allocator = mem.Allocator;

View File

@ -1,26 +1,3 @@
const UnwindInfo = @This();
const std = @import("std");
const assert = std.debug.assert;
const eh_frame = @import("eh_frame.zig");
const fs = std.fs;
const leb = std.leb;
const log = std.log.scoped(.unwind_info);
const macho = std.macho;
const math = std.math;
const mem = std.mem;
const trace = @import("../../tracy.zig").trace;
const Allocator = mem.Allocator;
const Atom = @import("ZldAtom.zig");
const AtomIndex = @import("zld.zig").AtomIndex;
const EhFrameRecord = eh_frame.EhFrameRecord;
const Object = @import("Object.zig");
const SymbolWithLoc = @import("zld.zig").SymbolWithLoc;
const Zld = @import("zld.zig").Zld;
const N_DEAD = @import("zld.zig").N_DEAD;
gpa: Allocator,
/// List of all unwind records gathered from all objects and sorted
@ -204,28 +181,28 @@ pub fn deinit(info: *UnwindInfo) void {
info.lsdas_lookup.deinit(info.gpa);
}
pub fn scanRelocs(zld: *Zld) !void {
if (zld.getSectionByName("__TEXT", "__unwind_info") == null) return;
pub fn scanRelocs(macho_file: *MachO) !void {
if (macho_file.unwind_info_section_index == null) return;
const cpu_arch = zld.options.target.cpu.arch;
for (zld.objects.items, 0..) |*object, object_id| {
const cpu_arch = macho_file.base.options.target.cpu.arch;
for (macho_file.objects.items, 0..) |*object, object_id| {
const unwind_records = object.getUnwindRecords();
for (object.exec_atoms.items) |atom_index| {
var inner_syms_it = Atom.getInnerSymbolsIterator(zld, atom_index);
var inner_syms_it = Atom.getInnerSymbolsIterator(macho_file, atom_index);
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;
const record = unwind_records[record_id];
if (!UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) {
if (getPersonalityFunctionReloc(zld, @as(u32, @intCast(object_id)), record_id)) |rel| {
if (getPersonalityFunctionReloc(macho_file, @as(u32, @intCast(object_id)), record_id)) |rel| {
// Personality function; add GOT pointer.
const target = Atom.parseRelocTarget(zld, .{
const target = Atom.parseRelocTarget(macho_file, .{
.object_id = @as(u32, @intCast(object_id)),
.rel = rel,
.code = mem.asBytes(&record),
.base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry))),
});
try Atom.addGotEntry(zld, target);
try macho_file.addGotEntry(target);
}
}
}
@ -233,10 +210,10 @@ pub fn scanRelocs(zld: *Zld) !void {
}
}
pub fn collect(info: *UnwindInfo, zld: *Zld) !void {
if (zld.getSectionByName("__TEXT", "__unwind_info") == null) return;
pub fn collect(info: *UnwindInfo, macho_file: *MachO) !void {
if (macho_file.unwind_info_section_index == null) return;
const cpu_arch = zld.options.target.cpu.arch;
const cpu_arch = macho_file.base.options.target.cpu.arch;
var records = std.ArrayList(macho.compact_unwind_entry).init(info.gpa);
defer records.deinit();
@ -245,7 +222,7 @@ pub fn collect(info: *UnwindInfo, zld: *Zld) !void {
defer sym_indexes.deinit();
// TODO handle dead stripping
for (zld.objects.items, 0..) |*object, object_id| {
for (macho_file.objects.items, 0..) |*object, object_id| {
log.debug("collecting unwind records in {s} ({d})", .{ object.name, object_id });
const unwind_records = object.getUnwindRecords();
@ -255,7 +232,7 @@ pub fn collect(info: *UnwindInfo, zld: *Zld) !void {
try sym_indexes.ensureUnusedCapacity(object.exec_atoms.items.len);
for (object.exec_atoms.items) |atom_index| {
var inner_syms_it = Atom.getInnerSymbolsIterator(zld, atom_index);
var inner_syms_it = Atom.getInnerSymbolsIterator(macho_file, atom_index);
var prev_symbol: ?SymbolWithLoc = null;
while (inner_syms_it.next()) |symbol| {
var record = if (object.unwind_records_lookup.get(symbol)) |record_id| blk: {
@ -263,14 +240,14 @@ pub fn collect(info: *UnwindInfo, zld: *Zld) !void {
var record = unwind_records[record_id];
if (UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) {
try info.collectPersonalityFromDwarf(zld, @as(u32, @intCast(object_id)), symbol, &record);
info.collectPersonalityFromDwarf(macho_file, @as(u32, @intCast(object_id)), symbol, &record);
} else {
if (getPersonalityFunctionReloc(
zld,
macho_file,
@as(u32, @intCast(object_id)),
record_id,
)) |rel| {
const target = Atom.parseRelocTarget(zld, .{
const target = Atom.parseRelocTarget(macho_file, .{
.object_id = @as(u32, @intCast(object_id)),
.rel = rel,
.code = mem.asBytes(&record),
@ -287,8 +264,8 @@ pub fn collect(info: *UnwindInfo, zld: *Zld) !void {
UnwindEncoding.setPersonalityIndex(&record.compactUnwindEncoding, personality_index + 1);
}
if (getLsdaReloc(zld, @as(u32, @intCast(object_id)), record_id)) |rel| {
const target = Atom.parseRelocTarget(zld, .{
if (getLsdaReloc(macho_file, @as(u32, @intCast(object_id)), record_id)) |rel| {
const target = Atom.parseRelocTarget(macho_file, .{
.object_id = @as(u32, @intCast(object_id)),
.rel = rel,
.code = mem.asBytes(&record),
@ -299,8 +276,8 @@ pub fn collect(info: *UnwindInfo, zld: *Zld) !void {
}
break :blk record;
} else blk: {
const sym = zld.getSymbol(symbol);
if (sym.n_desc == N_DEAD) continue;
const sym = macho_file.getSymbol(symbol);
if (sym.n_desc == MachO.N_DEAD) continue;
if (prev_symbol) |prev_sym| {
const prev_addr = object.getSourceSymbol(prev_sym.sym_index).?.n_value;
const curr_addr = object.getSourceSymbol(symbol.sym_index).?.n_value;
@ -311,7 +288,7 @@ pub fn collect(info: *UnwindInfo, zld: *Zld) !void {
if (object.eh_frame_records_lookup.get(symbol)) |fde_offset| {
if (object.eh_frame_relocs_lookup.get(fde_offset).?.dead) continue;
var record = nullRecord();
try info.collectPersonalityFromDwarf(zld, @as(u32, @intCast(object_id)), symbol, &record);
info.collectPersonalityFromDwarf(macho_file, @as(u32, @intCast(object_id)), symbol, &record);
switch (cpu_arch) {
.aarch64 => UnwindEncoding.setMode(&record.compactUnwindEncoding, macho.UNWIND_ARM64_MODE.DWARF),
.x86_64 => UnwindEncoding.setMode(&record.compactUnwindEncoding, macho.UNWIND_X86_64_MODE.DWARF),
@ -324,9 +301,9 @@ pub fn collect(info: *UnwindInfo, zld: *Zld) !void {
break :blk nullRecord();
};
const atom = zld.getAtom(atom_index);
const sym = zld.getSymbol(symbol);
assert(sym.n_desc != N_DEAD);
const atom = macho_file.getAtom(atom_index);
const sym = macho_file.getSymbol(symbol);
assert(sym.n_desc != MachO.N_DEAD);
const size = if (inner_syms_it.next()) |next_sym| blk: {
// All this trouble to account for symbol aliases.
// TODO I think that remodelling the linker so that a Symbol references an Atom
@ -337,8 +314,8 @@ pub fn collect(info: *UnwindInfo, zld: *Zld) !void {
const curr_addr = object.getSourceSymbol(symbol.sym_index).?.n_value;
const next_addr = object.getSourceSymbol(next_sym.sym_index).?.n_value;
if (next_addr > curr_addr) break :blk next_addr - curr_addr;
break :blk zld.getSymbol(atom.getSymbolWithLoc()).n_value + atom.size - sym.n_value;
} else zld.getSymbol(atom.getSymbolWithLoc()).n_value + atom.size - sym.n_value;
break :blk macho_file.getSymbol(atom.getSymbolWithLoc()).n_value + atom.size - sym.n_value;
} else macho_file.getSymbol(atom.getSymbolWithLoc()).n_value + atom.size - sym.n_value;
record.rangeStart = sym.n_value;
record.rangeLength = @as(u32, @intCast(size));
@ -519,23 +496,23 @@ pub fn collect(info: *UnwindInfo, zld: *Zld) !void {
fn collectPersonalityFromDwarf(
info: *UnwindInfo,
zld: *Zld,
macho_file: *MachO,
object_id: u32,
sym_loc: SymbolWithLoc,
record: *macho.compact_unwind_entry,
) !void {
const object = &zld.objects.items[object_id];
) void {
const object = &macho_file.objects.items[object_id];
var it = object.getEhFrameRecordsIterator();
const fde_offset = object.eh_frame_records_lookup.get(sym_loc).?;
it.seekTo(fde_offset);
const fde = (try it.next()).?;
const cie_ptr = fde.getCiePointerSource(object_id, zld, fde_offset);
const fde = (it.next() catch return).?; // We don't care about the error since we already handled it
const cie_ptr = fde.getCiePointerSource(object_id, macho_file, fde_offset);
const cie_offset = fde_offset + 4 - cie_ptr;
it.seekTo(cie_offset);
const cie = (try it.next()).?;
const cie = (it.next() catch return).?; // We don't care about the error since we already handled it
if (cie.getPersonalityPointerReloc(
zld,
macho_file,
@as(u32, @intCast(object_id)),
cie_offset,
)) |target| {
@ -551,9 +528,9 @@ fn collectPersonalityFromDwarf(
}
}
pub fn calcSectionSize(info: UnwindInfo, zld: *Zld) !void {
const sect_id = zld.getSectionByName("__TEXT", "__unwind_info") orelse return;
const sect = &zld.sections.items(.header)[sect_id];
pub fn calcSectionSize(info: UnwindInfo, macho_file: *MachO) void {
const sect_id = macho_file.unwind_info_section_index orelse return;
const sect = &macho_file.sections.items(.header)[sect_id];
sect.@"align" = 2;
sect.size = info.calcRequiredSize();
}
@ -570,25 +547,23 @@ fn calcRequiredSize(info: UnwindInfo) usize {
return total_size;
}
pub fn write(info: *UnwindInfo, zld: *Zld) !void {
const sect_id = zld.getSectionByName("__TEXT", "__unwind_info") orelse return;
const sect = &zld.sections.items(.header)[sect_id];
const seg_id = zld.sections.items(.segment_index)[sect_id];
const seg = zld.segments.items[seg_id];
pub fn write(info: *UnwindInfo, macho_file: *MachO) !void {
const sect_id = macho_file.unwind_info_section_index orelse return;
const sect = &macho_file.sections.items(.header)[sect_id];
const seg_id = macho_file.sections.items(.segment_index)[sect_id];
const seg = macho_file.segments.items[seg_id];
const text_sect_id = zld.getSectionByName("__TEXT", "__text").?;
const text_sect = zld.sections.items(.header)[text_sect_id];
const text_sect_id = macho_file.text_section_index.?;
const text_sect = macho_file.sections.items(.header)[text_sect_id];
var personalities: [max_personalities]u32 = undefined;
const cpu_arch = zld.options.target.cpu.arch;
const cpu_arch = macho_file.base.options.target.cpu.arch;
log.debug("Personalities:", .{});
for (info.personalities[0..info.personalities_count], 0..) |target, i| {
const atom_index = zld.getGotAtomIndexForSymbol(target).?;
const atom = zld.getAtom(atom_index);
const sym = zld.getSymbol(atom.getSymbolWithLoc());
personalities[i] = @as(u32, @intCast(sym.n_value - seg.vmaddr));
log.debug(" {d}: 0x{x} ({s})", .{ i, personalities[i], zld.getSymbolName(target) });
const addr = macho_file.getGotEntryAddress(target).?;
personalities[i] = @as(u32, @intCast(addr - seg.vmaddr));
log.debug(" {d}: 0x{x} ({s})", .{ i, personalities[i], macho_file.getSymbolName(target) });
}
for (info.records.items) |*rec| {
@ -602,7 +577,7 @@ pub fn write(info: *UnwindInfo, zld: *Zld) !void {
if (rec.compactUnwindEncoding > 0 and !UnwindEncoding.isDwarf(rec.compactUnwindEncoding, cpu_arch)) {
const lsda_target = @as(SymbolWithLoc, @bitCast(rec.lsda));
if (lsda_target.getFile()) |_| {
const sym = zld.getSymbol(lsda_target);
const sym = macho_file.getSymbol(lsda_target);
rec.lsda = sym.n_value - seg.vmaddr;
}
}
@ -692,11 +667,11 @@ pub fn write(info: *UnwindInfo, zld: *Zld) !void {
@memset(buffer.items[offset..], 0);
}
try zld.file.pwriteAll(buffer.items, sect.offset);
try macho_file.base.file.?.pwriteAll(buffer.items, sect.offset);
}
fn getRelocs(zld: *Zld, object_id: u32, record_id: usize) []const macho.relocation_info {
const object = &zld.objects.items[object_id];
fn getRelocs(macho_file: *MachO, object_id: u32, record_id: usize) []const macho.relocation_info {
const object = &macho_file.objects.items[object_id];
assert(object.hasUnwindRecords());
const rel_pos = object.unwind_relocs_lookup[record_id].reloc;
const relocs = object.getRelocs(object.unwind_info_sect_id.?);
@ -710,11 +685,11 @@ fn isPersonalityFunction(record_id: usize, rel: macho.relocation_info) bool {
}
pub fn getPersonalityFunctionReloc(
zld: *Zld,
macho_file: *MachO,
object_id: u32,
record_id: usize,
) ?macho.relocation_info {
const relocs = getRelocs(zld, object_id, record_id);
const relocs = getRelocs(macho_file, object_id, record_id);
for (relocs) |rel| {
if (isPersonalityFunction(record_id, rel)) return rel;
}
@ -738,8 +713,8 @@ fn isLsda(record_id: usize, rel: macho.relocation_info) bool {
return rel_offset == 24;
}
pub fn getLsdaReloc(zld: *Zld, object_id: u32, record_id: usize) ?macho.relocation_info {
const relocs = getRelocs(zld, object_id, record_id);
pub fn getLsdaReloc(macho_file: *MachO, object_id: u32, record_id: usize) ?macho.relocation_info {
const relocs = getRelocs(macho_file, object_id, record_id);
for (relocs) |rel| {
if (isLsda(record_id, rel)) return rel;
}
@ -831,3 +806,23 @@ pub const UnwindEncoding = struct {
enc.* |= offset;
}
};
const UnwindInfo = @This();
const std = @import("std");
const assert = std.debug.assert;
const eh_frame = @import("eh_frame.zig");
const fs = std.fs;
const leb = std.leb;
const log = std.log.scoped(.unwind_info);
const macho = std.macho;
const math = std.math;
const mem = std.mem;
const trace = @import("../../tracy.zig").trace;
const Allocator = mem.Allocator;
const Atom = @import("Atom.zig");
const EhFrameRecord = eh_frame.EhFrameRecord;
const MachO = @import("../MachO.zig");
const Object = @import("Object.zig");
const SymbolWithLoc = MachO.SymbolWithLoc;

File diff suppressed because it is too large Load Diff

View File

@ -1,92 +1,73 @@
//! An algorithm for dead stripping of unreferenced Atoms.
const std = @import("std");
const assert = std.debug.assert;
const eh_frame = @import("eh_frame.zig");
const log = std.log.scoped(.dead_strip);
const macho = std.macho;
const math = std.math;
const mem = std.mem;
const Allocator = mem.Allocator;
const AtomIndex = @import("zld.zig").AtomIndex;
const Atom = @import("ZldAtom.zig");
const SymbolWithLoc = @import("zld.zig").SymbolWithLoc;
const SymbolResolver = @import("zld.zig").SymbolResolver;
const UnwindInfo = @import("UnwindInfo.zig");
const Zld = @import("zld.zig").Zld;
const N_DEAD = @import("zld.zig").N_DEAD;
const AtomTable = std.AutoHashMap(AtomIndex, void);
pub fn gcAtoms(zld: *Zld, resolver: *const SymbolResolver) !void {
const gpa = zld.gpa;
pub fn gcAtoms(macho_file: *MachO) !void {
const gpa = macho_file.base.allocator;
var arena = std.heap.ArenaAllocator.init(gpa);
defer arena.deinit();
var roots = AtomTable.init(arena.allocator());
try roots.ensureUnusedCapacity(@as(u32, @intCast(zld.globals.items.len)));
try roots.ensureUnusedCapacity(@as(u32, @intCast(macho_file.globals.items.len)));
var alive = AtomTable.init(arena.allocator());
try alive.ensureTotalCapacity(@as(u32, @intCast(zld.atoms.items.len)));
try alive.ensureTotalCapacity(@as(u32, @intCast(macho_file.atoms.items.len)));
try collectRoots(zld, &roots, resolver);
try mark(zld, roots, &alive);
prune(zld, alive);
try collectRoots(macho_file, &roots);
mark(macho_file, roots, &alive);
prune(macho_file, alive);
}
fn addRoot(zld: *Zld, roots: *AtomTable, file: u32, sym_loc: SymbolWithLoc) !void {
const sym = zld.getSymbol(sym_loc);
fn addRoot(macho_file: *MachO, roots: *AtomTable, file: u32, sym_loc: SymbolWithLoc) !void {
const sym = macho_file.getSymbol(sym_loc);
assert(!sym.undf());
const object = &zld.objects.items[file];
const object = &macho_file.objects.items[file];
const atom_index = object.getAtomIndexForSymbol(sym_loc.sym_index).?; // panic here means fatal error
log.debug("root(ATOM({d}, %{d}, {d}))", .{
atom_index,
zld.getAtom(atom_index).sym_index,
macho_file.getAtom(atom_index).sym_index,
file,
});
_ = try roots.getOrPut(atom_index);
}
fn collectRoots(zld: *Zld, roots: *AtomTable, resolver: *const SymbolResolver) !void {
fn collectRoots(macho_file: *MachO, roots: *AtomTable) !void {
log.debug("collecting roots", .{});
switch (zld.options.output_mode) {
switch (macho_file.base.options.output_mode) {
.Exe => {
// Add entrypoint as GC root
const global: SymbolWithLoc = zld.getEntryPoint();
if (global.getFile()) |file| {
try addRoot(zld, roots, file, global);
} else {
assert(zld.getSymbol(global).undf()); // Stub as our entrypoint is in a dylib.
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 (zld.globals.items) |global| {
const sym = zld.getSymbol(global);
for (macho_file.globals.items) |global| {
const sym = macho_file.getSymbol(global);
if (sym.undf()) continue;
if (global.getFile()) |file| {
try addRoot(zld, roots, file, global);
try addRoot(macho_file, roots, file, global);
}
}
},
}
// Add all symbols force-defined by the user.
for (zld.options.force_undefined_symbols.keys()) |sym_name| {
const global_index = resolver.table.get(sym_name).?;
const global = zld.globals.items[global_index];
const sym = zld.getSymbol(global);
for (macho_file.base.options.force_undefined_symbols.keys()) |sym_name| {
const global_index = macho_file.resolver.get(sym_name).?;
const global = macho_file.globals.items[global_index];
const sym = macho_file.getSymbol(global);
assert(!sym.undf());
try addRoot(zld, roots, global.getFile().?, global);
try addRoot(macho_file, roots, global.getFile().?, global);
}
for (zld.objects.items) |object| {
for (macho_file.objects.items) |object| {
const has_subsections = object.header.flags & macho.MH_SUBSECTIONS_VIA_SYMBOLS != 0;
for (object.atoms.items) |atom_index| {
@ -95,7 +76,7 @@ fn collectRoots(zld: *Zld, roots: *AtomTable, resolver: *const SymbolResolver) !
// as a root.
if (!has_subsections) break :blk true;
const atom = zld.getAtom(atom_index);
const atom = macho_file.getAtom(atom_index);
const sect_id = if (object.getSourceSymbol(atom.sym_index)) |source_sym|
source_sym.n_sect - 1
else sect_id: {
@ -118,39 +99,39 @@ fn collectRoots(zld: *Zld, roots: *AtomTable, resolver: *const SymbolResolver) !
log.debug("root(ATOM({d}, %{d}, {?d}))", .{
atom_index,
zld.getAtom(atom_index).sym_index,
zld.getAtom(atom_index).getFile(),
macho_file.getAtom(atom_index).sym_index,
macho_file.getAtom(atom_index).getFile(),
});
}
}
}
}
fn markLive(zld: *Zld, atom_index: AtomIndex, alive: *AtomTable) void {
fn markLive(macho_file: *MachO, atom_index: Atom.Index, alive: *AtomTable) void {
if (alive.contains(atom_index)) return;
const atom = zld.getAtom(atom_index);
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 cpu_arch = zld.options.target.cpu.arch;
const cpu_arch = macho_file.base.options.target.cpu.arch;
const sym = zld.getSymbol(atom.getSymbolWithLoc());
const header = zld.sections.items(.header)[sym.n_sect - 1];
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(zld, atom_index);
const relocs = Atom.getAtomRelocs(zld, atom_index);
const ctx = Atom.getRelocContext(zld, atom_index);
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 target = switch (cpu_arch) {
.aarch64 => switch (@as(macho.reloc_type_arm64, @enumFromInt(rel.r_type))) {
.ARM64_RELOC_ADDEND => continue,
else => Atom.parseRelocTarget(zld, .{
else => Atom.parseRelocTarget(macho_file, .{
.object_id = atom.getFile().?,
.rel = rel,
.code = code,
@ -158,7 +139,7 @@ fn markLive(zld: *Zld, atom_index: AtomIndex, alive: *AtomTable) void {
.base_addr = ctx.base_addr,
}),
},
.x86_64 => Atom.parseRelocTarget(zld, .{
.x86_64 => Atom.parseRelocTarget(macho_file, .{
.object_id = atom.getFile().?,
.rel = rel,
.code = code,
@ -167,50 +148,50 @@ fn markLive(zld: *Zld, atom_index: AtomIndex, alive: *AtomTable) void {
}),
else => unreachable,
};
const target_sym = zld.getSymbol(target);
const target_sym = macho_file.getSymbol(target);
if (target_sym.undf()) continue;
if (target.getFile() == null) {
const target_sym_name = zld.getSymbolName(target);
const target_sym_name = macho_file.getSymbolName(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 = zld.objects.items[target.getFile().?];
const object = macho_file.objects.items[target.getFile().?];
const target_atom_index = object.getAtomIndexForSymbol(target.sym_index).?;
log.debug(" following ATOM({d}, %{d}, {?d})", .{
target_atom_index,
zld.getAtom(target_atom_index).sym_index,
zld.getAtom(target_atom_index).getFile(),
macho_file.getAtom(target_atom_index).sym_index,
macho_file.getAtom(target_atom_index).getFile(),
});
markLive(zld, target_atom_index, alive);
markLive(macho_file, target_atom_index, alive);
}
}
fn refersLive(zld: *Zld, atom_index: AtomIndex, alive: AtomTable) bool {
const atom = zld.getAtom(atom_index);
fn refersLive(macho_file: *MachO, atom_index: Atom.Index, alive: AtomTable) bool {
const atom = macho_file.getAtom(atom_index);
const sym_loc = atom.getSymbolWithLoc();
log.debug("refersLive(ATOM({d}, %{d}, {?d}))", .{ atom_index, sym_loc.sym_index, sym_loc.getFile() });
const cpu_arch = zld.options.target.cpu.arch;
const cpu_arch = macho_file.base.options.target.cpu.arch;
const sym = zld.getSymbol(sym_loc);
const header = zld.sections.items(.header)[sym.n_sect - 1];
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(zld, atom_index);
const relocs = Atom.getAtomRelocs(zld, atom_index);
const ctx = Atom.getRelocContext(zld, atom_index);
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 target = switch (cpu_arch) {
.aarch64 => switch (@as(macho.reloc_type_arm64, @enumFromInt(rel.r_type))) {
.ARM64_RELOC_ADDEND => continue,
else => Atom.parseRelocTarget(zld, .{
else => Atom.parseRelocTarget(macho_file, .{
.object_id = atom.getFile().?,
.rel = rel,
.code = code,
@ -218,7 +199,7 @@ fn refersLive(zld: *Zld, atom_index: AtomIndex, alive: AtomTable) bool {
.base_addr = ctx.base_addr,
}),
},
.x86_64 => Atom.parseRelocTarget(zld, .{
.x86_64 => Atom.parseRelocTarget(macho_file, .{
.object_id = atom.getFile().?,
.rel = rel,
.code = code,
@ -228,16 +209,16 @@ fn refersLive(zld: *Zld, atom_index: AtomIndex, alive: AtomTable) bool {
else => unreachable,
};
const object = zld.objects.items[target.getFile().?];
const object = macho_file.objects.items[target.getFile().?];
const target_atom_index = object.getAtomIndexForSymbol(target.sym_index) orelse {
log.debug("atom for symbol '{s}' not found; skipping...", .{zld.getSymbolName(target)});
log.debug("atom for symbol '{s}' not found; skipping...", .{macho_file.getSymbolName(target)});
continue;
};
if (alive.contains(target_atom_index)) {
log.debug(" refers live ATOM({d}, %{d}, {?d})", .{
target_atom_index,
zld.getAtom(target_atom_index).sym_index,
zld.getAtom(target_atom_index).getFile(),
macho_file.getAtom(target_atom_index).sym_index,
macho_file.getAtom(target_atom_index).getFile(),
});
return true;
}
@ -246,21 +227,21 @@ fn refersLive(zld: *Zld, atom_index: AtomIndex, alive: AtomTable) bool {
return false;
}
fn mark(zld: *Zld, roots: AtomTable, alive: *AtomTable) !void {
fn mark(macho_file: *MachO, roots: AtomTable, alive: *AtomTable) void {
var it = roots.keyIterator();
while (it.next()) |root| {
markLive(zld, root.*, alive);
markLive(macho_file, root.*, alive);
}
var loop: bool = true;
while (loop) {
loop = false;
for (zld.objects.items) |object| {
for (macho_file.objects.items) |object| {
for (object.atoms.items) |atom_index| {
if (alive.contains(atom_index)) continue;
const atom = zld.getAtom(atom_index);
const atom = macho_file.getAtom(atom_index);
const sect_id = if (object.getSourceSymbol(atom.sym_index)) |source_sym|
source_sym.n_sect - 1
else blk: {
@ -271,8 +252,8 @@ fn mark(zld: *Zld, roots: AtomTable, alive: *AtomTable) !void {
const source_sect = object.getSourceSection(sect_id);
if (source_sect.isDontDeadStripIfReferencesLive()) {
if (refersLive(zld, atom_index, alive.*)) {
markLive(zld, atom_index, alive);
if (refersLive(macho_file, atom_index, alive.*)) {
markLive(macho_file, atom_index, alive);
loop = true;
}
}
@ -280,26 +261,26 @@ fn mark(zld: *Zld, roots: AtomTable, alive: *AtomTable) !void {
}
}
for (zld.objects.items, 0..) |_, object_id| {
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.
try markUnwindRecords(zld, @as(u32, @intCast(object_id)), alive);
markUnwindRecords(macho_file, @as(u32, @intCast(object_id)), alive);
}
}
fn markUnwindRecords(zld: *Zld, object_id: u32, alive: *AtomTable) !void {
const object = &zld.objects.items[object_id];
const cpu_arch = zld.options.target.cpu.arch;
fn markUnwindRecords(macho_file: *MachO, object_id: u32, alive: *AtomTable) void {
const object = &macho_file.objects.items[object_id];
const cpu_arch = macho_file.base.options.target.cpu.arch;
const unwind_records = object.getUnwindRecords();
for (object.exec_atoms.items) |atom_index| {
var inner_syms_it = Atom.getInnerSymbolsIterator(zld, 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.
try markEhFrameRecords(zld, object_id, atom_index, alive);
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| {
@ -325,86 +306,86 @@ fn markUnwindRecords(zld: *Zld, object_id: u32, alive: *AtomTable) !void {
const record = unwind_records[record_id];
if (UnwindInfo.UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) {
try markEhFrameRecords(zld, object_id, atom_index, alive);
markEhFrameRecords(macho_file, object_id, atom_index, alive);
} else {
if (UnwindInfo.getPersonalityFunctionReloc(zld, object_id, record_id)) |rel| {
const target = Atom.parseRelocTarget(zld, .{
if (UnwindInfo.getPersonalityFunctionReloc(macho_file, object_id, record_id)) |rel| {
const 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 = zld.getSymbol(target);
const target_sym = macho_file.getSymbol(target);
if (!target_sym.undf()) {
const target_object = zld.objects.items[target.getFile().?];
const target_object = macho_file.objects.items[target.getFile().?];
const target_atom_index = target_object.getAtomIndexForSymbol(target.sym_index).?;
markLive(zld, target_atom_index, alive);
markLive(macho_file, target_atom_index, alive);
}
}
if (UnwindInfo.getLsdaReloc(zld, object_id, record_id)) |rel| {
const target = Atom.parseRelocTarget(zld, .{
if (UnwindInfo.getLsdaReloc(macho_file, object_id, record_id)) |rel| {
const 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 = zld.objects.items[target.getFile().?];
const target_object = macho_file.objects.items[target.getFile().?];
const target_atom_index = target_object.getAtomIndexForSymbol(target.sym_index).?;
markLive(zld, target_atom_index, alive);
markLive(macho_file, target_atom_index, alive);
}
}
}
}
}
fn markEhFrameRecords(zld: *Zld, object_id: u32, atom_index: AtomIndex, alive: *AtomTable) !void {
const cpu_arch = zld.options.target.cpu.arch;
const object = &zld.objects.items[object_id];
fn markEhFrameRecords(macho_file: *MachO, object_id: u32, atom_index: Atom.Index, alive: *AtomTable) void {
const cpu_arch = macho_file.base.options.target.cpu.arch;
const object = &macho_file.objects.items[object_id];
var it = object.getEhFrameRecordsIterator();
var inner_syms_it = Atom.getInnerSymbolsIterator(zld, atom_index);
var inner_syms_it = Atom.getInnerSymbolsIterator(macho_file, atom_index);
while (inner_syms_it.next()) |sym| {
const fde_offset = object.eh_frame_records_lookup.get(sym) orelse continue; // Continue in case we hit a temp symbol alias
it.seekTo(fde_offset);
const fde = (try it.next()).?;
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, zld, fde_offset);
const cie_ptr = fde.getCiePointerSource(object_id, macho_file, fde_offset);
const cie_offset = fde_offset + 4 - cie_ptr;
it.seekTo(cie_offset);
const cie = (try it.next()).?;
const cie = (it.next() catch continue).?; // We don't care about the error at this point since it was already handled
switch (cpu_arch) {
.aarch64 => {
// Mark FDE references which should include any referenced LSDA record
const relocs = eh_frame.getRelocs(zld, object_id, fde_offset);
const relocs = eh_frame.getRelocs(macho_file, object_id, fde_offset);
for (relocs) |rel| {
const target = Atom.parseRelocTarget(zld, .{
const 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 = zld.getSymbol(target);
const target_sym = macho_file.getSymbol(target);
if (!target_sym.undf()) blk: {
const target_object = zld.objects.items[target.getFile().?];
const target_object = macho_file.objects.items[target.getFile().?];
const target_atom_index = target_object.getAtomIndexForSymbol(target.sym_index) orelse
break :blk;
markLive(zld, target_atom_index, alive);
markLive(macho_file, target_atom_index, alive);
}
}
},
.x86_64 => {
const sect = object.getSourceSection(object.eh_frame_sect_id.?);
const lsda_ptr = try fde.getLsdaPointer(cie, .{
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(zld, target_atom_index, alive);
markLive(macho_file, target_atom_index, alive);
}
},
else => unreachable,
@ -412,20 +393,20 @@ fn markEhFrameRecords(zld: *Zld, object_id: u32, atom_index: AtomIndex, alive: *
// Mark CIE references which should include any referenced personalities
// that are defined locally.
if (cie.getPersonalityPointerReloc(zld, object_id, cie_offset)) |target| {
const target_sym = zld.getSymbol(target);
if (cie.getPersonalityPointerReloc(macho_file, object_id, cie_offset)) |target| {
const target_sym = macho_file.getSymbol(target);
if (!target_sym.undf()) {
const target_object = zld.objects.items[target.getFile().?];
const target_object = macho_file.objects.items[target.getFile().?];
const target_atom_index = target_object.getAtomIndexForSymbol(target.sym_index).?;
markLive(zld, target_atom_index, alive);
markLive(macho_file, target_atom_index, alive);
}
}
}
}
fn prune(zld: *Zld, alive: AtomTable) void {
fn prune(macho_file: *MachO, alive: AtomTable) void {
log.debug("pruning dead atoms", .{});
for (zld.objects.items) |*object| {
for (macho_file.objects.items) |*object| {
var i: usize = 0;
while (i < object.atoms.items.len) {
const atom_index = object.atoms.items[i];
@ -434,7 +415,7 @@ fn prune(zld: *Zld, alive: AtomTable) void {
continue;
}
const atom = zld.getAtom(atom_index);
const atom = macho_file.getAtom(atom_index);
const sym_loc = atom.getSymbolWithLoc();
log.debug("prune(ATOM({d}, %{d}, {?d}))", .{
@ -442,15 +423,15 @@ fn prune(zld: *Zld, alive: AtomTable) void {
sym_loc.sym_index,
sym_loc.getFile(),
});
log.debug(" {s} in {s}", .{ zld.getSymbolName(sym_loc), object.name });
log.debug(" {s} in {s}", .{ macho_file.getSymbolName(sym_loc), object.name });
const sym = zld.getSymbolPtr(sym_loc);
const sym = macho_file.getSymbolPtr(sym_loc);
const sect_id = sym.n_sect - 1;
var section = zld.sections.get(sect_id);
var section = macho_file.sections.get(sect_id);
section.header.size -= atom.size;
if (atom.prev_index) |prev_index| {
const prev = zld.getAtomPtr(prev_index);
const prev = macho_file.getAtomPtr(prev_index);
prev.next_index = atom.next_index;
} else {
if (atom.next_index) |next_index| {
@ -458,33 +439,49 @@ fn prune(zld: *Zld, alive: AtomTable) void {
}
}
if (atom.next_index) |next_index| {
const next = zld.getAtomPtr(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 = 0;
section.last_atom_index = 0;
section.first_atom_index = null;
section.last_atom_index = null;
}
}
zld.sections.set(sect_id, section);
macho_file.sections.set(sect_id, section);
_ = object.atoms.swapRemove(i);
sym.n_desc = N_DEAD;
sym.n_desc = MachO.N_DEAD;
var inner_sym_it = Atom.getInnerSymbolsIterator(zld, atom_index);
var inner_sym_it = Atom.getInnerSymbolsIterator(macho_file, atom_index);
while (inner_sym_it.next()) |inner| {
const inner_sym = zld.getSymbolPtr(inner);
inner_sym.n_desc = N_DEAD;
const inner_sym = macho_file.getSymbolPtr(inner);
inner_sym.n_desc = MachO.N_DEAD;
}
if (Atom.getSectionAlias(zld, atom_index)) |alias| {
const alias_sym = zld.getSymbolPtr(alias);
alias_sym.n_desc = 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 assert = std.debug.assert;
const eh_frame = @import("eh_frame.zig");
const log = std.log.scoped(.dead_strip);
const macho = std.macho;
const math = std.math;
const mem = std.mem;
const Allocator = mem.Allocator;
const Atom = @import("Atom.zig");
const MachO = @import("../MachO.zig");
const SymbolWithLoc = MachO.SymbolWithLoc;
const UnwindInfo = @import("UnwindInfo.zig");
const AtomTable = std.AutoHashMap(Atom.Index, void);

View File

@ -1,14 +1,3 @@
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) = .{},
buffer: std.ArrayListUnmanaged(u8) = .{},
@ -572,3 +561,14 @@ test "rebase - composite" {
macho.REBASE_OPCODE_DONE,
}, rebase.buffer.items);
}
const Rebase = @This();
const std = @import("std");
const assert = std.debug.assert;
const leb = std.leb;
const log = std.log.scoped(.dyld_info);
const macho = std.macho;
const testing = std.testing;
const Allocator = std.mem.Allocator;

View File

@ -1,12 +1,3 @@
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;
pub fn Bind(comptime Ctx: type, comptime Target: type) type {
return struct {
entries: std.ArrayListUnmanaged(Entry) = .{},
@ -738,3 +729,12 @@ test "lazy bind" {
macho.BIND_OPCODE_DONE,
}, bind.buffer.items);
}
const std = @import("std");
const assert = std.debug.assert;
const leb = std.leb;
const log = std.log.scoped(.dyld_info);
const macho = std.macho;
const testing = std.testing;
const Allocator = std.mem.Allocator;

View File

@ -1,68 +1,52 @@
const std = @import("std");
const assert = std.debug.assert;
const macho = std.macho;
const math = std.math;
const mem = std.mem;
const leb = std.leb;
const log = std.log.scoped(.eh_frame);
pub fn scanRelocs(macho_file: *MachO) !void {
const gpa = macho_file.base.allocator;
const Allocator = mem.Allocator;
const AtomIndex = @import("zld.zig").AtomIndex;
const Atom = @import("ZldAtom.zig");
const Relocation = @import("Relocation.zig");
const SymbolWithLoc = @import("zld.zig").SymbolWithLoc;
const UnwindInfo = @import("UnwindInfo.zig");
const Zld = @import("zld.zig").Zld;
pub fn scanRelocs(zld: *Zld) !void {
const gpa = zld.gpa;
for (zld.objects.items, 0..) |*object, object_id| {
for (macho_file.objects.items, 0..) |*object, object_id| {
var cies = std.AutoHashMap(u32, void).init(gpa);
defer cies.deinit();
var it = object.getEhFrameRecordsIterator();
for (object.exec_atoms.items) |atom_index| {
var inner_syms_it = Atom.getInnerSymbolsIterator(zld, atom_index);
var inner_syms_it = Atom.getInnerSymbolsIterator(macho_file, atom_index);
while (inner_syms_it.next()) |sym| {
const fde_offset = object.eh_frame_records_lookup.get(sym) orelse continue;
if (object.eh_frame_relocs_lookup.get(fde_offset).?.dead) continue;
it.seekTo(fde_offset);
const fde = (try it.next()).?;
const fde = (it.next() catch continue).?; // We don't care about this error since we already handled it
const cie_ptr = fde.getCiePointerSource(@intCast(object_id), zld, fde_offset);
const cie_ptr = fde.getCiePointerSource(@intCast(object_id), macho_file, fde_offset);
const cie_offset = fde_offset + 4 - cie_ptr;
if (!cies.contains(cie_offset)) {
try cies.putNoClobber(cie_offset, {});
it.seekTo(cie_offset);
const cie = (try it.next()).?;
try cie.scanRelocs(zld, @as(u32, @intCast(object_id)), cie_offset);
const cie = (it.next() catch continue).?; // We don't care about this error since we already handled it
try cie.scanRelocs(macho_file, @as(u32, @intCast(object_id)), cie_offset);
}
}
}
}
}
pub fn calcSectionSize(zld: *Zld, unwind_info: *const UnwindInfo) !void {
const sect_id = zld.getSectionByName("__TEXT", "__eh_frame") orelse return;
const sect = &zld.sections.items(.header)[sect_id];
pub fn calcSectionSize(macho_file: *MachO, unwind_info: *const UnwindInfo) error{OutOfMemory}!void {
const sect_id = macho_file.eh_frame_section_index orelse return;
const sect = &macho_file.sections.items(.header)[sect_id];
sect.@"align" = 3;
sect.size = 0;
const cpu_arch = zld.options.target.cpu.arch;
const gpa = zld.gpa;
const cpu_arch = macho_file.base.options.target.cpu.arch;
const gpa = macho_file.base.allocator;
var size: u32 = 0;
for (zld.objects.items, 0..) |*object, object_id| {
for (macho_file.objects.items, 0..) |*object, object_id| {
var cies = std.AutoHashMap(u32, u32).init(gpa);
defer cies.deinit();
var eh_it = object.getEhFrameRecordsIterator();
for (object.exec_atoms.items) |atom_index| {
var inner_syms_it = Atom.getInnerSymbolsIterator(zld, atom_index);
var inner_syms_it = Atom.getInnerSymbolsIterator(macho_file, atom_index);
while (inner_syms_it.next()) |sym| {
const fde_record_offset = object.eh_frame_records_lookup.get(sym) orelse continue;
if (object.eh_frame_relocs_lookup.get(fde_record_offset).?.dead) continue;
@ -75,15 +59,15 @@ pub fn calcSectionSize(zld: *Zld, unwind_info: *const UnwindInfo) !void {
if (!is_dwarf) continue;
eh_it.seekTo(fde_record_offset);
const source_fde_record = (try eh_it.next()).?;
const source_fde_record = (eh_it.next() catch continue).?; // We already handled this error
const cie_ptr = source_fde_record.getCiePointerSource(@intCast(object_id), zld, fde_record_offset);
const cie_ptr = source_fde_record.getCiePointerSource(@intCast(object_id), macho_file, fde_record_offset);
const cie_offset = fde_record_offset + 4 - cie_ptr;
const gop = try cies.getOrPut(cie_offset);
if (!gop.found_existing) {
eh_it.seekTo(cie_offset);
const source_cie_record = (try eh_it.next()).?;
const source_cie_record = (eh_it.next() catch continue).?; // We already handled this error
gop.value_ptr.* = size;
size += source_cie_record.getSize();
}
@ -96,14 +80,14 @@ pub fn calcSectionSize(zld: *Zld, unwind_info: *const UnwindInfo) !void {
}
}
pub fn write(zld: *Zld, unwind_info: *UnwindInfo) !void {
const sect_id = zld.getSectionByName("__TEXT", "__eh_frame") orelse return;
const sect = zld.sections.items(.header)[sect_id];
const seg_id = zld.sections.items(.segment_index)[sect_id];
const seg = zld.segments.items[seg_id];
pub fn write(macho_file: *MachO, unwind_info: *UnwindInfo) !void {
const sect_id = macho_file.eh_frame_section_index orelse return;
const sect = macho_file.sections.items(.header)[sect_id];
const seg_id = macho_file.sections.items(.segment_index)[sect_id];
const seg = macho_file.segments.items[seg_id];
const cpu_arch = zld.options.target.cpu.arch;
const gpa = zld.gpa;
const cpu_arch = macho_file.base.options.target.cpu.arch;
const gpa = macho_file.base.allocator;
var eh_records = std.AutoArrayHashMap(u32, EhFrameRecord(true)).init(gpa);
defer {
@ -115,7 +99,7 @@ pub fn write(zld: *Zld, unwind_info: *UnwindInfo) !void {
var eh_frame_offset: u32 = 0;
for (zld.objects.items, 0..) |*object, object_id| {
for (macho_file.objects.items, 0..) |*object, object_id| {
try eh_records.ensureUnusedCapacity(2 * @as(u32, @intCast(object.exec_atoms.items.len)));
var cies = std.AutoHashMap(u32, u32).init(gpa);
@ -124,7 +108,7 @@ pub fn write(zld: *Zld, unwind_info: *UnwindInfo) !void {
var eh_it = object.getEhFrameRecordsIterator();
for (object.exec_atoms.items) |atom_index| {
var inner_syms_it = Atom.getInnerSymbolsIterator(zld, atom_index);
var inner_syms_it = Atom.getInnerSymbolsIterator(macho_file, atom_index);
while (inner_syms_it.next()) |target| {
const fde_record_offset = object.eh_frame_records_lookup.get(target) orelse continue;
if (object.eh_frame_relocs_lookup.get(fde_record_offset).?.dead) continue;
@ -137,17 +121,17 @@ pub fn write(zld: *Zld, unwind_info: *UnwindInfo) !void {
if (!is_dwarf) continue;
eh_it.seekTo(fde_record_offset);
const source_fde_record = (try eh_it.next()).?;
const source_fde_record = (eh_it.next() catch continue).?; // We already handled this error
const cie_ptr = source_fde_record.getCiePointerSource(@intCast(object_id), zld, fde_record_offset);
const cie_ptr = source_fde_record.getCiePointerSource(@intCast(object_id), macho_file, fde_record_offset);
const cie_offset = fde_record_offset + 4 - cie_ptr;
const gop = try cies.getOrPut(cie_offset);
if (!gop.found_existing) {
eh_it.seekTo(cie_offset);
const source_cie_record = (try eh_it.next()).?;
const source_cie_record = (eh_it.next() catch continue).?; // We already handled this error
var cie_record = try source_cie_record.toOwned(gpa);
try cie_record.relocate(zld, @as(u32, @intCast(object_id)), .{
try cie_record.relocate(macho_file, @as(u32, @intCast(object_id)), .{
.source_offset = cie_offset,
.out_offset = eh_frame_offset,
.sect_addr = sect.addr,
@ -158,7 +142,7 @@ pub fn write(zld: *Zld, unwind_info: *UnwindInfo) !void {
}
var fde_record = try source_fde_record.toOwned(gpa);
try fde_record.relocate(zld, @as(u32, @intCast(object_id)), .{
try fde_record.relocate(macho_file, @as(u32, @intCast(object_id)), .{
.source_offset = fde_record_offset,
.out_offset = eh_frame_offset,
.sect_addr = sect.addr,
@ -169,7 +153,7 @@ pub fn write(zld: *Zld, unwind_info: *UnwindInfo) !void {
.aarch64 => {}, // relocs take care of LSDA pointers
.x86_64 => {
// We need to relocate target symbol address ourselves.
const atom_sym = zld.getSymbol(target);
const atom_sym = macho_file.getSymbol(target);
try fde_record.setTargetSymbolAddress(atom_sym.n_value, .{
.base_addr = sect.addr,
.base_offset = eh_frame_offset,
@ -180,17 +164,17 @@ pub fn write(zld: *Zld, unwind_info: *UnwindInfo) !void {
eh_frame_offset + 4 - fde_record.getCiePointer(),
).?;
const eh_frame_sect = object.getSourceSection(object.eh_frame_sect_id.?);
const source_lsda_ptr = try fde_record.getLsdaPointer(cie_record, .{
const source_lsda_ptr = fde_record.getLsdaPointer(cie_record, .{
.base_addr = eh_frame_sect.addr,
.base_offset = fde_record_offset,
});
}) catch continue; // We already handled this error
if (source_lsda_ptr) |ptr| {
const sym_index = object.getSymbolByAddress(ptr, null);
const sym = object.symtab[sym_index];
try fde_record.setLsdaPointer(cie_record, sym.n_value, .{
fde_record.setLsdaPointer(cie_record, sym.n_value, .{
.base_addr = sect.addr,
.base_offset = eh_frame_offset,
});
}) catch continue; // We already handled this error
}
},
else => unreachable,
@ -207,10 +191,10 @@ pub fn write(zld: *Zld, unwind_info: *UnwindInfo) !void {
const cie_record = eh_records.get(
eh_frame_offset + 4 - fde_record.getCiePointer(),
).?;
const lsda_ptr = try fde_record.getLsdaPointer(cie_record, .{
const lsda_ptr = fde_record.getLsdaPointer(cie_record, .{
.base_addr = sect.addr,
.base_offset = eh_frame_offset,
});
}) catch continue; // We already handled this error
if (lsda_ptr) |ptr| {
record.lsda = ptr - seg.vmaddr;
}
@ -229,7 +213,7 @@ pub fn write(zld: *Zld, unwind_info: *UnwindInfo) !void {
try buffer.appendSlice(record.data);
}
try zld.file.pwriteAll(buffer.items, sect.offset);
try macho_file.base.file.?.pwriteAll(buffer.items, sect.offset);
}
const EhFrameRecordTag = enum { cie, fde };
@ -261,12 +245,12 @@ pub fn EhFrameRecord(comptime is_mutable: bool) type {
pub fn scanRelocs(
rec: Record,
zld: *Zld,
macho_file: *MachO,
object_id: u32,
source_offset: u32,
) !void {
if (rec.getPersonalityPointerReloc(zld, object_id, source_offset)) |target| {
try Atom.addGotEntry(zld, target);
if (rec.getPersonalityPointerReloc(macho_file, object_id, source_offset)) |target| {
try macho_file.addGotEntry(target);
}
}
@ -290,12 +274,12 @@ pub fn EhFrameRecord(comptime is_mutable: bool) type {
pub fn getPersonalityPointerReloc(
rec: Record,
zld: *Zld,
macho_file: *MachO,
object_id: u32,
source_offset: u32,
) ?SymbolWithLoc {
const cpu_arch = zld.options.target.cpu.arch;
const relocs = getRelocs(zld, object_id, source_offset);
const cpu_arch = macho_file.base.options.target.cpu.arch;
const relocs = getRelocs(macho_file, object_id, source_offset);
for (relocs) |rel| {
switch (cpu_arch) {
.aarch64 => {
@ -317,7 +301,7 @@ pub fn EhFrameRecord(comptime is_mutable: bool) type {
},
else => unreachable,
}
const target = Atom.parseRelocTarget(zld, .{
const target = Atom.parseRelocTarget(macho_file, .{
.object_id = object_id,
.rel = rel,
.code = rec.data,
@ -328,18 +312,18 @@ pub fn EhFrameRecord(comptime is_mutable: bool) type {
return null;
}
pub fn relocate(rec: *Record, zld: *Zld, object_id: u32, ctx: struct {
pub fn relocate(rec: *Record, macho_file: *MachO, object_id: u32, ctx: struct {
source_offset: u32,
out_offset: u32,
sect_addr: u64,
}) !void {
comptime assert(is_mutable);
const cpu_arch = zld.options.target.cpu.arch;
const relocs = getRelocs(zld, object_id, ctx.source_offset);
const cpu_arch = macho_file.base.options.target.cpu.arch;
const relocs = getRelocs(macho_file, object_id, ctx.source_offset);
for (relocs) |rel| {
const target = Atom.parseRelocTarget(zld, .{
const target = Atom.parseRelocTarget(macho_file, .{
.object_id = object_id,
.rel = rel,
.code = rec.data,
@ -356,14 +340,14 @@ pub fn EhFrameRecord(comptime is_mutable: bool) type {
// Address of the __eh_frame in the source object file
},
.ARM64_RELOC_POINTER_TO_GOT => {
const target_addr = try Atom.getRelocTargetAddress(zld, target, true, false);
const target_addr = macho_file.getGotEntryAddress(target).?;
const result = math.cast(i32, @as(i64, @intCast(target_addr)) - @as(i64, @intCast(source_addr))) orelse
return error.Overflow;
mem.writeIntLittle(i32, rec.data[rel_offset..][0..4], result);
},
.ARM64_RELOC_UNSIGNED => {
assert(rel.r_extern == 1);
const target_addr = try Atom.getRelocTargetAddress(zld, target, false, false);
const target_addr = Atom.getRelocTargetAddress(macho_file, target, false);
const result = @as(i64, @intCast(target_addr)) - @as(i64, @intCast(source_addr));
mem.writeIntLittle(i64, rec.data[rel_offset..][0..8], @as(i64, @intCast(result)));
},
@ -374,7 +358,7 @@ pub fn EhFrameRecord(comptime is_mutable: bool) type {
const rel_type = @as(macho.reloc_type_x86_64, @enumFromInt(rel.r_type));
switch (rel_type) {
.X86_64_RELOC_GOT => {
const target_addr = try Atom.getRelocTargetAddress(zld, target, true, false);
const target_addr = macho_file.getGotEntryAddress(target).?;
const addend = mem.readIntLittle(i32, rec.data[rel_offset..][0..4]);
const adjusted_target_addr = @as(u64, @intCast(@as(i64, @intCast(target_addr)) + addend));
const disp = try Relocation.calcPcRelativeDisplacementX86(source_addr, adjusted_target_addr, 0);
@ -388,20 +372,20 @@ pub fn EhFrameRecord(comptime is_mutable: bool) type {
}
}
pub fn getCiePointerSource(rec: Record, object_id: u32, zld: *Zld, offset: u32) u32 {
pub fn getCiePointerSource(rec: Record, object_id: u32, macho_file: *MachO, offset: u32) u32 {
assert(rec.tag == .fde);
const cpu_arch = zld.options.target.cpu.arch;
const cpu_arch = macho_file.base.options.target.cpu.arch;
const addend = mem.readIntLittle(u32, rec.data[0..4]);
switch (cpu_arch) {
.aarch64 => {
const relocs = getRelocs(zld, object_id, offset);
const relocs = getRelocs(macho_file, object_id, offset);
const maybe_rel = for (relocs) |rel| {
if (rel.r_address - @as(i32, @intCast(offset)) == 4 and
@as(macho.reloc_type_arm64, @enumFromInt(rel.r_type)) == .ARM64_RELOC_SUBTRACTOR)
break rel;
} else null;
const rel = maybe_rel orelse return addend;
const object = &zld.objects.items[object_id];
const object = &macho_file.objects.items[object_id];
const target_addr = object.in_symtab.?[rel.r_symbolnum].n_value;
const sect = object.getSourceSection(object.eh_frame_sect_id.?);
return @intCast(sect.addr + offset - target_addr + addend);
@ -583,8 +567,8 @@ pub fn EhFrameRecord(comptime is_mutable: bool) type {
};
}
pub fn getRelocs(zld: *Zld, object_id: u32, source_offset: u32) []const macho.relocation_info {
const object = &zld.objects.items[object_id];
pub fn getRelocs(macho_file: *MachO, object_id: u32, source_offset: u32) []const macho.relocation_info {
const object = &macho_file.objects.items[object_id];
assert(object.hasEhFrameRecords());
const urel = object.eh_frame_relocs_lookup.get(source_offset) orelse
return &[0]macho.relocation_info{};
@ -604,7 +588,7 @@ pub const Iterator = struct {
var size = try reader.readIntLittle(u32);
if (size == 0xFFFFFFFF) {
log.err("MachO doesn't support 64bit DWARF CFI __eh_frame records", .{});
log.debug("MachO doesn't support 64bit DWARF CFI __eh_frame records", .{});
return error.BadDwarfCfi;
}
@ -650,3 +634,18 @@ pub const EH_PE = struct {
pub const indirect = 0x80;
pub const omit = 0xFF;
};
const std = @import("std");
const assert = std.debug.assert;
const macho = std.macho;
const math = std.math;
const mem = std.mem;
const leb = std.leb;
const log = std.log.scoped(.eh_frame);
const Allocator = mem.Allocator;
const Atom = @import("Atom.zig");
const MachO = @import("../MachO.zig");
const Relocation = @import("Relocation.zig");
const SymbolWithLoc = MachO.SymbolWithLoc;
const UnwindInfo = @import("UnwindInfo.zig");

View File

@ -1,42 +1,44 @@
const std = @import("std");
const log = std.log.scoped(.archive);
const macho = std.macho;
const mem = std.mem;
pub fn decodeArch(cputype: macho.cpu_type_t, comptime logError: bool) !std.Target.Cpu.Arch {
const cpu_arch: std.Target.Cpu.Arch = switch (cputype) {
macho.CPU_TYPE_ARM64 => .aarch64,
macho.CPU_TYPE_X86_64 => .x86_64,
else => {
if (logError) {
log.err("unsupported cpu architecture 0x{x}", .{cputype});
}
return error.UnsupportedCpuArchitecture;
},
};
return cpu_arch;
pub fn isFatLibrary(file: std.fs.File) bool {
const reader = file.reader();
const hdr = reader.readStructBig(macho.fat_header) catch return false;
defer file.seekTo(0) catch {};
return hdr.magic == macho.FAT_MAGIC;
}
pub fn getLibraryOffset(reader: anytype, cpu_arch: std.Target.Cpu.Arch) !u64 {
pub const Arch = struct {
tag: std.Target.Cpu.Arch,
offset: u64,
};
/// Caller owns the memory.
pub fn parseArchs(gpa: Allocator, file: std.fs.File) ![]const Arch {
const reader = file.reader();
const fat_header = try reader.readStructBig(macho.fat_header);
if (fat_header.magic != macho.FAT_MAGIC) return 0;
assert(fat_header.magic == macho.FAT_MAGIC);
var archs = try std.ArrayList(Arch).initCapacity(gpa, fat_header.nfat_arch);
defer archs.deinit();
var fat_arch_index: u32 = 0;
while (fat_arch_index < fat_header.nfat_arch) : (fat_arch_index += 1) {
const fat_arch = try reader.readStructBig(macho.fat_arch);
// If we come across an architecture that we do not know how to handle, that's
// fine because we can keep looking for one that might match.
const lib_arch = decodeArch(fat_arch.cputype, false) catch |err| switch (err) {
error.UnsupportedCpuArchitecture => continue,
const arch: std.Target.Cpu.Arch = switch (fat_arch.cputype) {
macho.CPU_TYPE_ARM64 => if (fat_arch.cpusubtype == macho.CPU_SUBTYPE_ARM_ALL) .aarch64 else continue,
macho.CPU_TYPE_X86_64 => if (fat_arch.cpusubtype == macho.CPU_SUBTYPE_X86_64_ALL) .x86_64 else continue,
else => continue,
};
if (lib_arch == cpu_arch) {
// We have found a matching architecture!
return fat_arch.offset;
}
} else {
log.err("Could not find matching cpu architecture in fat library: expected {s}", .{
@tagName(cpu_arch),
});
return error.MismatchedCpuArchitecture;
archs.appendAssumeCapacity(.{ .tag = arch, .offset = fat_arch.offset });
}
return archs.toOwnedSlice();
}
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;

View File

@ -1,12 +1,3 @@
const std = @import("std");
const assert = std.debug.assert;
const fs = std.fs;
const mem = std.mem;
const Allocator = mem.Allocator;
const ThreadPool = std.Thread.Pool;
const WaitGroup = std.Thread.WaitGroup;
pub fn ParallelHasher(comptime Hasher: type) type {
const hash_size = Hasher.digest_length;
@ -69,3 +60,12 @@ pub fn ParallelHasher(comptime Hasher: type) type {
const Self = @This();
};
}
const std = @import("std");
const assert = std.debug.assert;
const fs = std.fs;
const mem = std.mem;
const Allocator = mem.Allocator;
const ThreadPool = std.Thread.Pool;
const WaitGroup = std.Thread.WaitGroup;

View File

@ -1,13 +1,3 @@
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");
/// Default implicit entrypoint symbol name.
pub const default_entry_point: []const u8 = "_main";
@ -86,8 +76,14 @@ fn calcLCsSize(gpa: Allocator, options: *const link.Options, ctx: CalcLCsSizeCtx
}
// LC_SOURCE_VERSION
sizeofcmds += @sizeOf(macho.source_version_command);
// LC_BUILD_VERSION
sizeofcmds += @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version);
// LC_BUILD_VERSION or LC_VERSION_MIN_
if (Platform.fromTarget(options.target).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);
}
// LC_UUID
sizeofcmds += @sizeOf(macho.uuid_command);
// LC_LOAD_DYLIB
@ -101,17 +97,8 @@ fn calcLCsSize(gpa: Allocator, options: *const link.Options, ctx: CalcLCsSizeCtx
);
}
// LC_CODE_SIGNATURE
{
const target = options.target;
const requires_codesig = blk: {
if (options.entitlements) |_| break :blk true;
if (target.cpu.arch == .aarch64 and (target.os.tag == .macos or target.abi == .simulator))
break :blk true;
break :blk false;
};
if (requires_codesig) {
sizeofcmds += @sizeOf(macho.linkedit_data_command);
}
if (MachO.requiresCodeSignature(options)) {
sizeofcmds += @sizeOf(macho.linkedit_data_command);
}
return @as(u32, @intCast(sizeofcmds));
@ -271,33 +258,28 @@ pub fn writeRpathLCs(gpa: Allocator, options: *const link.Options, lc_writer: an
}
}
pub fn writeBuildVersionLC(options: *const link.Options, lc_writer: anytype) !void {
pub fn writeVersionMinLC(platform: Platform, sdk_version: ?std.SemanticVersion, lc_writer: anytype) !void {
const cmd: macho.LC = switch (platform.os_tag) {
.macos => .VERSION_MIN_MACOSX,
.ios => .VERSION_MIN_IPHONEOS,
.tvos => .VERSION_MIN_TVOS,
.watchos => .VERSION_MIN_WATCHOS,
else => unreachable,
};
try lc_writer.writeAll(mem.asBytes(&macho.version_min_command{
.cmd = cmd,
.version = platform.toAppleVersion(),
.sdk = if (sdk_version) |ver| semanticVersionToAppleVersion(ver) else platform.toAppleVersion(),
}));
}
pub fn writeBuildVersionLC(platform: Platform, sdk_version: ?std.SemanticVersion, lc_writer: anytype) !void {
const cmdsize = @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version);
const platform_version = blk: {
const ver = options.target.os.version_range.semver.min;
const platform_version = @as(u32, @intCast(ver.major << 16 | ver.minor << 8));
break :blk platform_version;
};
const sdk_version: ?std.SemanticVersion = options.darwin_sdk_version orelse blk: {
if (options.sysroot) |path| break :blk inferSdkVersionFromSdkPath(path);
break :blk null;
};
const sdk_version_value: u32 = if (sdk_version) |ver|
@intCast(ver.major << 16 | ver.minor << 8)
else
platform_version;
const is_simulator_abi = options.target.abi == .simulator;
try lc_writer.writeStruct(macho.build_version_command{
.cmdsize = cmdsize,
.platform = switch (options.target.os.tag) {
.macos => .MACOS,
.ios => if (is_simulator_abi) macho.PLATFORM.IOSSIMULATOR else macho.PLATFORM.IOS,
.watchos => if (is_simulator_abi) macho.PLATFORM.WATCHOSSIMULATOR else macho.PLATFORM.WATCHOS,
.tvos => if (is_simulator_abi) macho.PLATFORM.TVOSSIMULATOR else macho.PLATFORM.TVOS,
else => unreachable,
},
.minos = platform_version,
.sdk = sdk_version_value,
.platform = platform.toApplePlatform(),
.minos = platform.toAppleVersion(),
.sdk = if (sdk_version) |ver| semanticVersionToAppleVersion(ver) else platform.toAppleVersion(),
.ntools = 1,
});
try lc_writer.writeAll(mem.asBytes(&macho.build_tool_version{
@ -320,7 +302,160 @@ pub fn writeLoadDylibLCs(dylibs: []const Dylib, referenced: []u16, lc_writer: an
}
}
fn inferSdkVersionFromSdkPath(path: []const u8) ?std.SemanticVersion {
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 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 inferSdkVersionFromSdkPath(path: []const u8) ?std.SemanticVersion {
const stem = std.fs.path.stem(path);
const start = for (stem, 0..) |c, i| {
if (std.ascii.isDigit(c)) break i;
@ -374,3 +509,14 @@ test "parseSdkVersion" {
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");

View File

@ -1,9 +1,4 @@
const std = @import("std");
const aarch64 = @import("../../arch/aarch64/bits.zig");
const Relocation = @import("Relocation.zig");
pub inline fn calcStubHelperPreambleSize(cpu_arch: std.Target.Cpu.Arch) u5 {
pub inline fn stubHelperPreambleSize(cpu_arch: std.Target.Cpu.Arch) u8 {
return switch (cpu_arch) {
.x86_64 => 15,
.aarch64 => 6 * @sizeOf(u32),
@ -11,7 +6,7 @@ pub inline fn calcStubHelperPreambleSize(cpu_arch: std.Target.Cpu.Arch) u5 {
};
}
pub inline fn calcStubHelperEntrySize(cpu_arch: std.Target.Cpu.Arch) u4 {
pub inline fn stubHelperSize(cpu_arch: std.Target.Cpu.Arch) u8 {
return switch (cpu_arch) {
.x86_64 => 10,
.aarch64 => 3 * @sizeOf(u32),
@ -19,7 +14,7 @@ pub inline fn calcStubHelperEntrySize(cpu_arch: std.Target.Cpu.Arch) u4 {
};
}
pub inline fn calcStubEntrySize(cpu_arch: std.Target.Cpu.Arch) u4 {
pub inline fn stubSize(cpu_arch: std.Target.Cpu.Arch) u8 {
return switch (cpu_arch) {
.x86_64 => 6,
.aarch64 => 3 * @sizeOf(u32),
@ -27,7 +22,15 @@ pub inline fn calcStubEntrySize(cpu_arch: std.Target.Cpu.Arch) u4 {
};
}
pub inline fn calcStubOffsetInStubHelper(cpu_arch: std.Target.Cpu.Arch) u4 {
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),
@ -159,3 +162,8 @@ pub fn writeStubCode(args: struct {
else => unreachable,
}
}
const std = @import("std");
const aarch64 = @import("../../arch/aarch64/bits.zig");
const Relocation = @import("Relocation.zig");

View File

@ -5,24 +5,6 @@
//! The algorithm works pessimistically and assumes that any reference to an Atom in
//! another output section is out of range.
const std = @import("std");
const assert = std.debug.assert;
const log = std.log.scoped(.thunks);
const macho = std.macho;
const math = std.math;
const mem = std.mem;
const aarch64 = @import("../../arch/aarch64/bits.zig");
const Allocator = mem.Allocator;
const Atom = @import("ZldAtom.zig");
const AtomIndex = @import("zld.zig").AtomIndex;
const Relocation = @import("Relocation.zig");
const SymbolWithLoc = @import("zld.zig").SymbolWithLoc;
const Zld = @import("zld.zig").Zld;
pub const ThunkIndex = u32;
/// Branch instruction has 26 bits immediate but 4 byte aligned.
const jump_bits = @bitSizeOf(i28);
@ -35,21 +17,35 @@ const max_distance = (1 << (jump_bits - 1));
const max_allowed_distance = max_distance - 0x500_000;
pub const Thunk = struct {
start_index: AtomIndex,
start_index: Atom.Index,
len: u32,
lookup: std.AutoArrayHashMapUnmanaged(SymbolWithLoc, AtomIndex) = .{},
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) AtomIndex {
pub fn getStartAtomIndex(self: Thunk) Atom.Index {
assert(self.len != 0);
return self.start_index;
}
pub fn getEndAtomIndex(self: Thunk) AtomIndex {
pub fn getEndAtomIndex(self: Thunk) Atom.Index {
assert(self.len != 0);
return self.start_index + self.len - 1;
}
@ -62,19 +58,18 @@ pub const Thunk = struct {
return @alignOf(u32);
}
pub fn getTrampolineForSymbol(self: Thunk, zld: *Zld, target: SymbolWithLoc) ?SymbolWithLoc {
const atom_index = self.lookup.get(target) orelse return null;
const atom = zld.getAtom(atom_index);
return atom.getSymbolWithLoc();
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(zld: *Zld, sect_id: u8) !void {
const header = &zld.sections.items(.header)[sect_id];
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 gpa = zld.gpa;
const first_atom_index = zld.sections.items(.first_atom_index)[sect_id];
const gpa = macho_file.base.allocator;
const first_atom_index = macho_file.sections.items(.first_atom_index)[sect_id].?;
header.size = 0;
header.@"align" = 0;
@ -84,8 +79,8 @@ pub fn createThunks(zld: *Zld, sect_id: u8) !void {
{
var atom_index = first_atom_index;
while (true) {
const atom = zld.getAtom(atom_index);
const sym = zld.getSymbolPtr(atom.getSymbolWithLoc());
const atom = macho_file.getAtom(atom_index);
const sym = macho_file.getSymbolPtr(atom.getSymbolWithLoc());
sym.n_value = 0;
atom_count += 1;
@ -95,7 +90,7 @@ pub fn createThunks(zld: *Zld, sect_id: u8) !void {
}
}
var allocated = std.AutoHashMap(AtomIndex, void).init(gpa);
var allocated = std.AutoHashMap(Atom.Index, void).init(gpa);
defer allocated.deinit();
try allocated.ensureTotalCapacity(atom_count);
@ -104,24 +99,24 @@ pub fn createThunks(zld: *Zld, sect_id: u8) !void {
var offset: u64 = 0;
while (true) {
const group_start_atom = zld.getAtom(group_start);
const group_start_atom = macho_file.getAtom(group_start);
log.debug("GROUP START at {d}", .{group_start});
while (true) {
const atom = zld.getAtom(group_end);
const atom = macho_file.getAtom(group_end);
offset = mem.alignForward(u64, offset, try math.powi(u32, 2, atom.alignment));
const sym = zld.getSymbolPtr(atom.getSymbolWithLoc());
const sym = macho_file.getSymbolPtr(atom.getSymbolWithLoc());
sym.n_value = offset;
offset += atom.size;
zld.logAtom(group_end, log);
macho_file.logAtom(group_end, log);
header.@"align" = @max(header.@"align", atom.alignment);
allocated.putAssumeCapacityNoClobber(group_end, {});
const group_start_sym = zld.getSymbol(group_start_atom.getSymbolWithLoc());
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| {
@ -131,15 +126,15 @@ pub fn createThunks(zld: *Zld, sect_id: u8) !void {
log.debug("GROUP END at {d}", .{group_end});
// Insert thunk at group_end
const thunk_index = @as(u32, @intCast(zld.thunks.items.len));
try zld.thunks.append(gpa, .{ .start_index = undefined, .len = 0 });
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 = zld.getAtom(atom_index);
const atom = macho_file.getAtom(atom_index);
try scanRelocs(
zld,
macho_file,
atom_index,
allocated,
thunk_index,
@ -154,19 +149,19 @@ pub fn createThunks(zld: *Zld, sect_id: u8) !void {
}
offset = mem.alignForward(u64, offset, Thunk.getAlignment());
allocateThunk(zld, thunk_index, offset, header);
offset += zld.thunks.items[thunk_index].getSize();
allocateThunk(macho_file, thunk_index, offset, header);
offset += macho_file.thunks.items[thunk_index].getSize();
const thunk = zld.thunks.items[thunk_index];
const thunk = macho_file.thunks.items[thunk_index];
if (thunk.len == 0) {
const group_end_atom = zld.getAtom(group_end);
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 = zld.getAtom(thunk_end_atom_index);
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;
@ -178,12 +173,12 @@ pub fn createThunks(zld: *Zld, sect_id: u8) !void {
}
fn allocateThunk(
zld: *Zld,
thunk_index: ThunkIndex,
macho_file: *MachO,
thunk_index: Thunk.Index,
base_offset: u64,
header: *macho.section_64,
) void {
const thunk = zld.thunks.items[thunk_index];
const thunk = macho_file.thunks.items[thunk_index];
if (thunk.len == 0) return;
const first_atom_index = thunk.getStartAtomIndex();
@ -192,14 +187,14 @@ fn allocateThunk(
var atom_index = first_atom_index;
var offset = base_offset;
while (true) {
const atom = zld.getAtom(atom_index);
const atom = macho_file.getAtom(atom_index);
offset = mem.alignForward(u64, offset, Thunk.getAlignment());
const sym = zld.getSymbolPtr(atom.getSymbolWithLoc());
const sym = macho_file.getSymbolPtr(atom.getSymbolWithLoc());
sym.n_value = offset;
offset += atom.size;
zld.logAtom(atom_index, log);
macho_file.logAtom(atom_index, log);
header.@"align" = @max(header.@"align", atom.alignment);
@ -212,162 +207,163 @@ fn allocateThunk(
}
fn scanRelocs(
zld: *Zld,
atom_index: AtomIndex,
allocated: std.AutoHashMap(AtomIndex, void),
thunk_index: ThunkIndex,
group_end: AtomIndex,
macho_file: *MachO,
atom_index: Atom.Index,
allocated: std.AutoHashMap(Atom.Index, void),
thunk_index: Thunk.Index,
group_end: Atom.Index,
) !void {
const atom = zld.getAtom(atom_index);
const object = zld.objects.items[atom.getFile().?];
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(zld, atom_index);
const relocs = Atom.getAtomRelocs(zld, atom_index);
const ctx = Atom.getRelocContext(zld, atom_index);
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(zld, .{
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(zld, atom_index, rel, base_offset, target, allocated)) continue;
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,
zld.getSymbolName(atom.getSymbolWithLoc()),
zld.getSymbol(atom.getSymbolWithLoc()).n_value,
zld.getSymbolName(target),
zld.getSymbol(target).n_value,
macho_file.getSymbolName(atom.getSymbolWithLoc()),
macho_file.getSymbol(atom.getSymbolWithLoc()).n_value,
macho_file.getSymbolName(target),
macho_file.getSymbol(target).n_value,
});
const gpa = zld.gpa;
const target_sym = zld.getSymbol(target);
const gpa = macho_file.base.allocator;
const target_sym = macho_file.getSymbol(target);
const thunk = &macho_file.thunks.items[thunk_index];
const actual_target: SymbolWithLoc = if (target_sym.undf()) blk: {
const stub_atom_index = zld.getStubsAtomIndexForSymbol(target).?;
break :blk .{ .sym_index = zld.getAtom(stub_atom_index).sym_index };
} else target;
const thunk = &zld.thunks.items[thunk_index];
const gop = try thunk.lookup.getOrPut(gpa, actual_target);
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) {
const thunk_atom_index = try createThunkAtom(zld);
gop.value_ptr.* = thunk_atom_index;
const thunk_atom = zld.getAtomPtr(thunk_atom_index);
const end_atom_index = if (thunk.len == 0) group_end else thunk.getEndAtomIndex();
const end_atom = zld.getAtomPtr(end_atom_index);
if (end_atom.next_index) |first_after_index| {
const first_after_atom = zld.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;
gop.value_ptr.* = try pushThunkAtom(macho_file, thunk, group_end);
try thunk.targets.append(gpa, thunk_target);
}
try zld.thunk_table.put(gpa, atom_index, thunk_index);
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(
zld: *Zld,
atom_index: AtomIndex,
macho_file: *MachO,
atom_index: Atom.Index,
rel: macho.relocation_info,
base_offset: i32,
target: SymbolWithLoc,
allocated: std.AutoHashMap(AtomIndex, void),
allocated: std.AutoHashMap(Atom.Index, void),
) bool {
if (zld.getStubsAtomIndexForSymbol(target)) |_| return false;
if (macho_file.stub_table.lookup.contains(target)) return false;
const source_atom = zld.getAtom(atom_index);
const source_sym = zld.getSymbol(source_atom.getSymbolWithLoc());
const source_atom = macho_file.getAtom(atom_index);
const source_sym = macho_file.getSymbol(source_atom.getSymbolWithLoc());
const target_object = zld.objects.items[target.getFile().?];
const target_object = macho_file.objects.items[target.getFile().?];
const target_atom_index = target_object.getAtomIndexForSymbol(target.sym_index).?;
const target_atom = zld.getAtom(target_atom_index);
const target_sym = zld.getSymbol(target_atom.getSymbolWithLoc());
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 is_via_got = Atom.relocRequiresGot(zld, rel);
const target_addr = Atom.getRelocTargetAddress(zld, target, is_via_got, false) catch unreachable;
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(zld: *Zld) !AtomIndex {
const sym_index = try zld.allocateSymbol();
const atom_index = try zld.createEmptyAtom(sym_index, @sizeOf(u32) * 3, 2);
const sym = zld.getSymbolPtr(.{ .sym_index = sym_index });
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 = 2 });
const sym = macho_file.getSymbolPtr(.{ .sym_index = sym_index });
sym.n_type = macho.N_SECT;
const sect_id = zld.getSectionByName("__TEXT", "__text") orelse unreachable;
sym.n_sect = sect_id + 1;
sym.n_sect = macho_file.text_section_index.? + 1;
return atom_index;
}
fn getThunkIndex(zld: *Zld, atom_index: AtomIndex) ?ThunkIndex {
const atom = zld.getAtom(atom_index);
const sym = zld.getSymbol(atom.getSymbolWithLoc());
for (zld.thunks.items, 0..) |thunk, i| {
if (thunk.len == 0) continue;
const thunk_atom_index = thunk.getStartAtomIndex();
const thunk_atom = zld.getAtom(thunk_atom_index);
const thunk_sym = zld.getSymbol(thunk_atom.getSymbolWithLoc());
const start_addr = thunk_sym.n_value;
const end_addr = start_addr + thunk.getSize();
if (start_addr <= sym.n_value and sym.n_value < end_addr) {
return @as(u32, @intCast(i));
}
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.writeIntLittle(u32, aarch64.Instruction.adrp(.x16, pages).toU32());
const off = try Relocation.calcPageOffset(target_addr, .arithmetic);
try writer.writeIntLittle(u32, aarch64.Instruction.add(.x16, .x16, off, false).toU32());
try writer.writeIntLittle(u32, aarch64.Instruction.br(.x16).toU32());
}
return null;
}
pub fn writeThunkCode(zld: *Zld, atom_index: AtomIndex, writer: anytype) !void {
const atom = zld.getAtom(atom_index);
const sym = zld.getSymbol(atom.getSymbolWithLoc());
const source_addr = sym.n_value;
const thunk = zld.thunks.items[getThunkIndex(zld, atom_index).?];
const target_addr = for (thunk.lookup.keys()) |target| {
const target_atom_index = thunk.lookup.get(target).?;
if (atom_index == target_atom_index) break zld.getSymbol(target).n_value;
} else unreachable;
const std = @import("std");
const assert = std.debug.assert;
const log = std.log.scoped(.thunks);
const macho = std.macho;
const math = std.math;
const mem = std.mem;
const pages = Relocation.calcNumberOfPages(source_addr, target_addr);
try writer.writeIntLittle(u32, aarch64.Instruction.adrp(.x16, pages).toU32());
const off = try Relocation.calcPageOffset(target_addr, .arithmetic);
try writer.writeIntLittle(u32, aarch64.Instruction.add(.x16, .x16, off, false).toU32());
try writer.writeIntLittle(u32, aarch64.Instruction.br(.x16).toU32());
}
const aarch64 = @import("../../arch/aarch64/bits.zig");
const Allocator = mem.Allocator;
const Atom = @import("Atom.zig");
const MachO = @import("../MachO.zig");
const Relocation = @import("Relocation.zig");
const SymbolWithLoc = MachO.SymbolWithLoc;

View File

@ -1,12 +1,3 @@
const std = @import("std");
const fs = std.fs;
const mem = std.mem;
const Allocator = mem.Allocator;
const Compilation = @import("../../Compilation.zig");
const Md5 = std.crypto.hash.Md5;
const Hasher = @import("hasher.zig").ParallelHasher;
/// Calculates Md5 hash of each chunk in parallel and then hashes all Md5 hashes to produce
/// the final digest.
/// While this is NOT a correct MD5 hash of the contents, this methodology is used by LLVM/LLD
@ -43,3 +34,12 @@ inline fn conform(out: *[Md5.digest_length]u8) void {
out[6] = (out[6] & 0x0F) | (3 << 4);
out[8] = (out[8] & 0x3F) | 0x80;
}
const std = @import("std");
const fs = std.fs;
const mem = std.mem;
const Allocator = mem.Allocator;
const Compilation = @import("../../Compilation.zig");
const Md5 = std.crypto.hash.Md5;
const Hasher = @import("hasher.zig").ParallelHasher;

File diff suppressed because it is too large Load Diff

View File

@ -2,9 +2,10 @@ const std = @import("std");
const fs = std.fs;
const mem = std.mem;
const log = std.log.scoped(.tapi);
const yaml = @import("tapi/yaml.zig");
const Allocator = mem.Allocator;
const Yaml = @import("tapi/yaml.zig").Yaml;
const Yaml = yaml.Yaml;
const VersionField = union(enum) {
string: []const u8,
@ -80,6 +81,30 @@ pub const Tbd = union(enum) {
v3: TbdV3,
v4: TbdV4,
/// Caller owns memory.
pub fn targets(self: Tbd, gpa: Allocator) error{OutOfMemory}![]const []const u8 {
var out = std.ArrayList([]const u8).init(gpa);
defer out.deinit();
switch (self) {
.v3 => |v3| {
try out.ensureTotalCapacityPrecise(v3.archs.len);
for (v3.archs) |arch| {
const target = try std.fmt.allocPrint(gpa, "{s}-{s}", .{ arch, v3.platform });
out.appendAssumeCapacity(target);
}
},
.v4 => |v4| {
try out.ensureTotalCapacityPrecise(v4.targets.len);
for (v4.targets) |t| {
out.appendAssumeCapacity(try gpa.dupe(u8, t));
}
},
}
return out.toOwnedSlice();
}
pub fn currentVersion(self: Tbd) ?VersionField {
return switch (self) {
.v3 => |v3| v3.current_version,
@ -102,6 +127,11 @@ pub const Tbd = union(enum) {
}
};
pub const TapiError = error{
NotLibStub,
FileTooBig,
} || yaml.YamlError || std.fs.File.ReadError;
pub const LibStub = struct {
/// Underlying memory for stub's contents.
yaml: Yaml,
@ -109,7 +139,7 @@ pub const LibStub = struct {
/// Typed contents of the tbd file.
inner: []Tbd,
pub fn loadFromFile(allocator: Allocator, file: fs.File) !LibStub {
pub fn loadFromFile(allocator: Allocator, file: fs.File) TapiError!LibStub {
const source = try file.readToEndAlloc(allocator, std.math.maxInt(u32));
defer allocator.free(source);