macho: revamp how we compute dyld relocs

This commit is contained in:
Jakub Konka 2024-07-05 08:16:23 +02:00
parent 101299e856
commit e2bfd6fc69
8 changed files with 655 additions and 807 deletions

View File

@ -82,11 +82,11 @@ stubs_helper: StubsHelperSection = .{},
objc_stubs: ObjcStubsSection = .{},
la_symbol_ptr: LaSymbolPtrSection = .{},
tlv_ptr: TlvPtrSection = .{},
rebase: RebaseSection = .{},
bind: BindSection = .{},
weak_bind: WeakBindSection = .{},
lazy_bind: LazyBindSection = .{},
export_trie: ExportTrieSection = .{},
rebase: Rebase = .{},
bind: Bind = .{},
weak_bind: WeakBind = .{},
lazy_bind: LazyBind = .{},
export_trie: ExportTrie = .{},
unwind_info: UnwindInfo = .{},
/// Tracked loadable segments during incremental linking.
@ -590,8 +590,6 @@ pub fn flushModule(self: *MachO, arena: Allocator, tid: Zcu.PerThread.Id, prog_n
state_log.debug("{}", .{self.dumpState()});
}
try self.initDyldInfoSections();
// Beyond this point, everything has been allocated a virtual address and we can resolve
// the relocations, and commit objects to file.
if (self.getZigObject()) |zo| {
@ -2500,87 +2498,6 @@ fn allocateLinkeditSegment(self: *MachO) !void {
seg.fileoff = mem.alignForward(u64, fileoff, page_size);
}
fn initDyldInfoSections(self: *MachO) !void {
const tracy = trace(@src());
defer tracy.end();
const gpa = self.base.comp.gpa;
if (self.zig_got_sect_index != null) try self.zig_got.addDyldRelocs(self);
if (self.got_sect_index != null) try self.got.addDyldRelocs(self);
if (self.tlv_ptr_sect_index != null) try self.tlv_ptr.addDyldRelocs(self);
if (self.la_symbol_ptr_sect_index != null) try self.la_symbol_ptr.addDyldRelocs(self);
try self.initExportTrie();
var objects = try std.ArrayList(File.Index).initCapacity(gpa, self.objects.items.len + 1);
defer objects.deinit();
if (self.getZigObject()) |zo| objects.appendAssumeCapacity(zo.index);
objects.appendSliceAssumeCapacity(self.objects.items);
var nrebases: usize = 0;
var nbinds: usize = 0;
var nweak_binds: usize = 0;
for (objects.items) |index| {
const ctx = switch (self.getFile(index).?) {
.zig_object => |x| x.dynamic_relocs,
.object => |x| x.dynamic_relocs,
else => unreachable,
};
nrebases += ctx.rebase_relocs;
nbinds += ctx.bind_relocs;
nweak_binds += ctx.weak_bind_relocs;
}
if (self.getInternalObject()) |int| {
nrebases += int.num_rebase_relocs;
}
try self.rebase.entries.ensureUnusedCapacity(gpa, nrebases);
try self.bind.entries.ensureUnusedCapacity(gpa, nbinds);
try self.weak_bind.entries.ensureUnusedCapacity(gpa, nweak_binds);
}
fn initExportTrie(self: *MachO) !void {
const tracy = trace(@src());
defer tracy.end();
const gpa = self.base.comp.gpa;
try self.export_trie.init(gpa);
const seg = self.getTextSegment();
for (self.objects.items) |index| {
for (self.getFile(index).?.getSymbols()) |sym_index| {
const sym = self.getSymbol(sym_index);
if (!sym.flags.@"export") continue;
if (sym.getAtom(self)) |atom| if (!atom.flags.alive) continue;
if (sym.getFile(self).?.getIndex() != index) continue;
var flags: u64 = if (sym.flags.abs)
macho.EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE
else if (sym.flags.tlv)
macho.EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL
else
macho.EXPORT_SYMBOL_FLAGS_KIND_REGULAR;
if (sym.flags.weak) {
flags |= macho.EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION;
self.weak_defines = true;
self.binds_to_weak = true;
}
try self.export_trie.put(gpa, .{
.name = sym.getName(self),
.vmaddr_offset = sym.getAddress(.{ .stubs = false }, self) - seg.vmaddr,
.export_flags = flags,
});
}
}
if (self.mh_execute_header_index) |index| {
const sym = self.getSymbol(index);
try self.export_trie.put(gpa, .{
.name = sym.getName(self),
.vmaddr_offset = sym.getAddress(.{}, self) - seg.vmaddr,
.export_flags = macho.EXPORT_SYMBOL_FLAGS_KIND_REGULAR,
});
}
}
fn writeAtoms(self: *MachO) !void {
const tracy = trace(@src());
defer tracy.end();
@ -2659,13 +2576,13 @@ fn writeUnwindInfo(self: *MachO) !void {
fn finalizeDyldInfoSections(self: *MachO) !void {
const tracy = trace(@src());
defer tracy.end();
const gpa = self.base.comp.gpa;
try self.rebase.finalize(gpa);
try self.bind.finalize(gpa, self);
try self.weak_bind.finalize(gpa, self);
try self.lazy_bind.finalize(gpa, self);
try self.export_trie.finalize(gpa);
try self.rebase.updateSize(self);
try self.bind.updateSize(self);
try self.weak_bind.updateSize(self);
if (self.la_symbol_ptr_sect_index) |_| {
try self.lazy_bind.updateSize(self);
}
try self.export_trie.updateSize(self);
}
fn writeSyntheticSections(self: *MachO) !void {
@ -2742,25 +2659,14 @@ fn writeDyldInfoSections(self: *MachO, off: u32) !u32 {
const gpa = self.base.comp.gpa;
const cmd = &self.dyld_info_cmd;
var needed_size: u32 = 0;
cmd.rebase_off = needed_size;
cmd.rebase_size = mem.alignForward(u32, @intCast(self.rebase.size()), @alignOf(u64));
needed_size += cmd.rebase_size;
cmd.bind_off = needed_size;
cmd.bind_size = mem.alignForward(u32, @intCast(self.bind.size()), @alignOf(u64));
needed_size += cmd.bind_size;
cmd.weak_bind_off = needed_size;
cmd.weak_bind_size = mem.alignForward(u32, @intCast(self.weak_bind.size()), @alignOf(u64));
needed_size += cmd.weak_bind_size;
cmd.lazy_bind_off = needed_size;
cmd.lazy_bind_size = mem.alignForward(u32, @intCast(self.lazy_bind.size()), @alignOf(u64));
needed_size += cmd.lazy_bind_size;
cmd.export_off = needed_size;
cmd.export_size = mem.alignForward(u32, @intCast(self.export_trie.size), @alignOf(u64));
needed_size += cmd.export_size;
const buffer = try gpa.alloc(u8, needed_size);
@ -2785,7 +2691,6 @@ fn writeDyldInfoSections(self: *MachO, off: u32) !u32 {
cmd.weak_bind_off += off;
cmd.lazy_bind_off += off;
cmd.export_off += off;
try self.base.file.?.pwriteAll(buffer, off);
return off + needed_size;
@ -4831,6 +4736,7 @@ const mem = std.mem;
const meta = std.meta;
const aarch64 = @import("../arch/aarch64/bits.zig");
const bind = @import("MachO/dyld_info/bind.zig");
const calcUuid = @import("MachO/uuid.zig").calcUuid;
const codegen = @import("../codegen.zig");
const dead_strip = @import("MachO/dead_strip.zig");
@ -4851,13 +4757,13 @@ const Alignment = Atom.Alignment;
const Allocator = mem.Allocator;
const Archive = @import("MachO/Archive.zig");
pub const Atom = @import("MachO/Atom.zig");
const BindSection = synthetic.BindSection;
const Bind = bind.Bind;
const Cache = std.Build.Cache;
const CodeSignature = @import("MachO/CodeSignature.zig");
const Compilation = @import("../Compilation.zig");
pub const DebugSymbols = @import("MachO/DebugSymbols.zig");
const Dylib = @import("MachO/Dylib.zig");
const ExportTrieSection = synthetic.ExportTrieSection;
const ExportTrie = @import("MachO/dyld_info/Trie.zig");
const File = @import("MachO/file.zig").File;
const GotSection = synthetic.GotSection;
const Hash = std.hash.Wyhash;
@ -4865,7 +4771,7 @@ const Indsymtab = synthetic.Indsymtab;
const InternalObject = @import("MachO/InternalObject.zig");
const ObjcStubsSection = synthetic.ObjcStubsSection;
const Object = @import("MachO/Object.zig");
const LazyBindSection = synthetic.LazyBindSection;
const LazyBind = bind.LazyBind;
const LaSymbolPtrSection = synthetic.LaSymbolPtrSection;
const LibStub = tapi.LibStub;
const Liveness = @import("../Liveness.zig");
@ -4875,7 +4781,7 @@ const Zcu = @import("../Zcu.zig");
/// Deprecated.
const Module = Zcu;
const InternPool = @import("../InternPool.zig");
const RebaseSection = synthetic.RebaseSection;
const Rebase = @import("MachO/dyld_info/Rebase.zig");
pub const Relocation = @import("MachO/Relocation.zig");
const StringTable = @import("StringTable.zig");
const StubsSection = synthetic.StubsSection;
@ -4885,6 +4791,6 @@ const Thunk = thunks.Thunk;
const TlvPtrSection = synthetic.TlvPtrSection;
const Value = @import("../Value.zig");
const UnwindInfo = @import("MachO/UnwindInfo.zig");
const WeakBindSection = synthetic.WeakBindSection;
const WeakBind = bind.WeakBind;
const ZigGotSection = synthetic.ZigGotSection;
const ZigObject = @import("MachO/ZigObject.zig");

View File

@ -460,11 +460,6 @@ pub fn scanRelocs(self: Atom, macho_file: *MachO) !void {
defer tracy.end();
assert(self.flags.alive);
const dynrel_ctx = switch (self.getFile(macho_file)) {
.zig_object => |x| &x.dynamic_relocs,
.object => |x| &x.dynamic_relocs,
else => unreachable,
};
const relocs = self.getRelocs(macho_file);
for (relocs) |rel| {
@ -537,21 +532,15 @@ pub fn scanRelocs(self: Atom, macho_file: *MachO) !void {
continue;
}
if (symbol.flags.import) {
dynrel_ctx.bind_relocs += 1;
if (symbol.flags.weak) {
dynrel_ctx.weak_bind_relocs += 1;
macho_file.binds_to_weak = true;
}
continue;
}
if (symbol.flags.@"export" and symbol.flags.weak) {
dynrel_ctx.weak_bind_relocs += 1;
macho_file.binds_to_weak = true;
} else if (symbol.flags.interposable) {
dynrel_ctx.bind_relocs += 1;
}
}
dynrel_ctx.rebase_relocs += 1;
}
},
@ -651,8 +640,6 @@ fn resolveRelocInner(
) ResolveError!void {
const cpu_arch = macho_file.getTarget().cpu.arch;
const rel_offset = math.cast(usize, rel.offset - self.off) orelse return error.Overflow;
const seg_id = macho_file.sections.items(.segment_id)[self.out_n_sect];
const seg = macho_file.segments.items[seg_id];
const P = @as(i64, @intCast(self.getAddress(macho_file))) + @as(i64, @intCast(rel_offset));
const A = rel.addend + rel.getRelocAddend(cpu_arch);
const S: i64 = @intCast(rel.getTargetAddress(macho_file));
@ -706,29 +693,8 @@ fn resolveRelocInner(
try writer.writeInt(u64, @intCast(S - TLS), .little);
return;
}
const entry = bind.Entry{
.target = rel.target,
.offset = @as(u64, @intCast(P)) - seg.vmaddr,
.segment_id = seg_id,
.addend = A,
};
if (sym.flags.import) {
macho_file.bind.entries.appendAssumeCapacity(entry);
if (sym.flags.weak) {
macho_file.weak_bind.entries.appendAssumeCapacity(entry);
}
return;
}
if (sym.flags.@"export" and sym.flags.weak) {
macho_file.weak_bind.entries.appendAssumeCapacity(entry);
} else if (sym.flags.interposable) {
macho_file.bind.entries.appendAssumeCapacity(entry);
}
if (sym.flags.import) return;
}
macho_file.rebase.entries.appendAssumeCapacity(.{
.offset = @as(u64, @intCast(P)) - seg.vmaddr,
.segment_id = seg_id,
});
try writer.writeInt(u64, @bitCast(S + A - SUB), .little);
} else if (rel.meta.length == 2) {
try writer.writeInt(u32, @bitCast(@as(i32, @truncate(S + A - SUB))), .little);
@ -1239,7 +1205,6 @@ pub const Alignment = @import("../../InternPool.zig").Alignment;
const aarch64 = @import("../aarch64.zig");
const assert = std.debug.assert;
const bind = @import("dyld_info/bind.zig");
const macho = std.macho;
const math = std.math;
const mem = std.mem;

View File

@ -30,7 +30,6 @@ data_in_code: std.ArrayListUnmanaged(macho.data_in_code_entry) = .{},
alive: bool = true,
hidden: bool = false,
dynamic_relocs: MachO.DynamicRelocs = .{},
output_symtab_ctx: MachO.SymtabCtx = .{},
output_ar_state: Archive.ArState = .{},

View File

@ -48,7 +48,6 @@ relocs: RelocationTable = .{},
dwarf: ?Dwarf = null,
dynamic_relocs: MachO.DynamicRelocs = .{},
output_symtab_ctx: MachO.SymtabCtx = .{},
output_ar_state: Archive.ArState = .{},

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(.link_dyld_info);
const macho = std.macho;
const testing = std.testing;
const Allocator = std.mem.Allocator;
entries: std.ArrayListUnmanaged(Entry) = .{},
buffer: std.ArrayListUnmanaged(u8) = .{},
@ -30,11 +19,94 @@ pub fn deinit(rebase: *Rebase, gpa: Allocator) void {
rebase.buffer.deinit(gpa);
}
pub fn size(rebase: Rebase) u64 {
return @as(u64, @intCast(rebase.buffer.items.len));
pub fn updateSize(rebase: *Rebase, macho_file: *MachO) !void {
const tracy = trace(@src());
defer tracy.end();
const gpa = macho_file.base.comp.gpa;
var objects = try std.ArrayList(File.Index).initCapacity(gpa, macho_file.objects.items.len + 1);
defer objects.deinit();
objects.appendSliceAssumeCapacity(macho_file.objects.items);
if (macho_file.getInternalObject()) |obj| objects.appendAssumeCapacity(obj.index);
for (objects.items) |index| {
const file = macho_file.getFile(index).?;
for (file.getAtoms()) |atom_index| {
const atom = macho_file.getAtom(atom_index) orelse continue;
if (!atom.flags.alive) continue;
if (atom.getInputSection(macho_file).isZerofill()) continue;
const atom_addr = atom.getAddress(macho_file);
const seg_id = macho_file.sections.items(.segment_id)[atom.out_n_sect];
const seg = macho_file.segments.items[seg_id];
for (atom.getRelocs(macho_file)) |rel| {
if (rel.type != .unsigned or rel.meta.length != 3) continue;
if (rel.tag == .@"extern") {
const sym = rel.getTargetSymbol(macho_file);
if (sym.isTlvInit(macho_file)) continue;
if (sym.flags.import) continue;
}
const rel_offset = rel.offset - atom.off;
try rebase.entries.append(gpa, .{
.offset = atom_addr + rel_offset - seg.vmaddr,
.segment_id = seg_id,
});
}
}
}
if (macho_file.got_sect_index) |sid| {
const seg_id = macho_file.sections.items(.segment_id)[sid];
const seg = macho_file.segments.items[seg_id];
for (macho_file.got.symbols.items, 0..) |ref, idx| {
const sym = macho_file.getSymbol(ref);
const addr = macho_file.got.getAddress(@intCast(idx), macho_file);
if (!sym.flags.import) {
try rebase.entries.append(gpa, .{
.offset = addr - seg.vmaddr,
.segment_id = seg_id,
});
}
}
}
if (macho_file.la_symbol_ptr_sect_index) |sid| {
const sect = macho_file.sections.items(.header)[sid];
const seg_id = macho_file.sections.items(.segment_id)[sid];
const seg = macho_file.segments.items[seg_id];
for (macho_file.stubs.symbols.items, 0..) |ref, idx| {
const sym = macho_file.getSymbol(ref);
const addr = sect.addr + idx * @sizeOf(u64);
const rebase_entry = Rebase.Entry{
.offset = addr - seg.vmaddr,
.segment_id = seg_id,
};
if ((sym.flags.import and !sym.flags.weak) or !sym.flags.import) {
try rebase.entries.append(gpa, rebase_entry);
}
}
}
if (macho_file.tlv_ptr_sect_index) |sid| {
const seg_id = macho_file.sections.items(.segment_id)[sid];
const seg = macho_file.segments.items[seg_id];
for (macho_file.tlv_ptr.symbols.items, 0..) |ref, idx| {
const sym = macho_file.getSymbol(ref);
const addr = macho_file.tlv_ptr.getAddress(@intCast(idx), macho_file);
if (!sym.flags.import) {
try rebase.entries.append(gpa, .{
.offset = addr - seg.vmaddr,
.segment_id = seg_id,
});
}
}
}
try rebase.finalize(gpa);
macho_file.dyld_info_cmd.rebase_size = mem.alignForward(u32, @intCast(rebase.buffer.items.len), @alignOf(u64));
}
pub fn finalize(rebase: *Rebase, gpa: Allocator) !void {
fn finalize(rebase: *Rebase, gpa: Allocator) !void {
if (rebase.entries.items.len == 0) return;
const writer = rebase.buffer.writer(gpa);
@ -198,7 +270,6 @@ fn done(writer: anytype) !void {
}
pub fn write(rebase: Rebase, writer: anytype) !void {
if (rebase.size() == 0) return;
try writer.writeAll(rebase.buffer.items);
}
@ -574,3 +645,17 @@ test "rebase - composite" {
macho.REBASE_OPCODE_DONE,
}, rebase.buffer.items);
}
const std = @import("std");
const assert = std.debug.assert;
const leb = std.leb;
const log = std.log.scoped(.link_dyld_info);
const macho = std.macho;
const mem = std.mem;
const testing = std.testing;
const trace = @import("../../../tracy.zig").trace;
const Allocator = mem.Allocator;
const File = @import("../file.zig").File;
const MachO = @import("../../MachO.zig");
const Rebase = @This();

View File

@ -28,269 +28,270 @@
//! 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(.macho);
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 };
const 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);
const 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,
root: ?Node.Index = null,
buffer: std.ArrayListUnmanaged(u8) = .{},
nodes: std.MultiArrayList(Node) = .{},
edges: std.ArrayListUnmanaged(Edge) = .{},
/// If you want to access nodes ordered in DFS fashion,
/// you should call `finalize` first since the nodes
/// in this container are not guaranteed to not be stale
/// if more insertions took place after the last `finalize`
/// call.
ordered_nodes: std.ArrayListUnmanaged(*Node) = .{},
/// Insert a symbol into the trie, updating the prefixes in the process.
/// This operation may change the layout of the trie by splicing edges in
/// certain circumstances.
fn put(self: *Trie, allocator: Allocator, symbol: ExportSymbol) !void {
// const tracy = trace(@src());
// defer tracy.end();
/// The size of the trie in bytes.
/// This value may be outdated if there were additional
/// insertions performed after `finalize` was called.
/// Call `finalize` before accessing this value to ensure
/// it is up-to-date.
size: u64 = 0,
const node_index = try self.putNode(self.root.?, allocator, symbol.name);
const slice = self.nodes.slice();
slice.items(.is_terminal)[node_index] = true;
slice.items(.vmaddr_offset)[node_index] = symbol.vmaddr_offset;
slice.items(.export_flags)[node_index] = symbol.export_flags;
}
/// Number of nodes currently in the trie.
node_count: usize = 0,
/// Inserts a new node starting at `node_index`.
fn putNode(self: *Trie, node_index: Node.Index, allocator: Allocator, label: []const u8) !Node.Index {
// Check for match with edges from this node.
for (self.nodes.items(.edges)[node_index].items) |edge_index| {
const edge = &self.edges.items[edge_index];
const match = mem.indexOfDiff(u8, edge.label, label) orelse return edge.node;
if (match == 0) continue;
if (match == edge.label.len) return self.putNode(edge.node, allocator, label[match..]);
trie_dirty: bool = true,
// Found a match, need to splice up nodes.
// From: A -> B
// To: A -> C -> B
const mid_index = try self.addNode(allocator);
const to_label = edge.label[match..];
const to_node = edge.node;
edge.node = mid_index;
edge.label = label[0..match];
const new_edge_index = try self.addEdge(allocator);
const new_edge = &self.edges.items[new_edge_index];
new_edge.node = to_node;
new_edge.label = to_label;
try self.nodes.items(.edges)[mid_index].append(allocator, new_edge_index);
return if (match == label.len) mid_index else self.putNode(mid_index, allocator, label[match..]);
}
// Add a new node.
const new_node_index = try self.addNode(allocator);
const new_edge_index = try self.addEdge(allocator);
const new_edge = &self.edges.items[new_edge_index];
new_edge.node = new_node_index;
new_edge.label = label;
try self.nodes.items(.edges)[node_index].append(allocator, new_edge_index);
return new_node_index;
}
pub fn updateSize(self: *Trie, macho_file: *MachO) !void {
const tracy = trace(@src());
defer tracy.end();
const gpa = macho_file.base.comp.gpa;
try self.init(gpa);
// TODO
// try self.nodes.ensureUnusedCapacity(gpa, macho_file.resolver.values.items.len * 2);
// try self.edges.ensureUnusedCapacity(gpa, macho_file.resolver.values.items.len * 2);
const seg = macho_file.getTextSegment();
for (macho_file.objects.items) |index| {
for (macho_file.getFile(index).?.getSymbols()) |ref| {
const sym = macho_file.getSymbol(ref);
if (!sym.flags.@"export") continue;
if (sym.getAtom(macho_file)) |atom| if (!atom.flags.alive) continue;
var flags: u64 = if (sym.flags.abs)
macho.EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE
else if (sym.flags.tlv)
macho.EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL
else
macho.EXPORT_SYMBOL_FLAGS_KIND_REGULAR;
if (sym.flags.weak) {
flags |= macho.EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION;
macho_file.weak_defines = true;
macho_file.binds_to_weak = true;
}
try self.put(gpa, .{
.name = sym.getName(macho_file),
.vmaddr_offset = sym.getAddress(.{ .stubs = false }, macho_file) - seg.vmaddr,
.export_flags = flags,
});
}
}
try self.finalize(gpa);
macho_file.dyld_info_cmd.export_size = mem.alignForward(u32, @intCast(self.buffer.items.len), @alignOf(u64));
}
/// Finalizes this trie for writing to a byte stream.
/// This step performs multiple passes through the trie ensuring
/// there are no gaps after every `Node` is ULEB128 encoded.
/// Call this method before trying to `write` the trie to a byte stream.
fn finalize(self: *Trie, allocator: Allocator) !void {
const tracy = trace(@src());
defer tracy.end();
var ordered_nodes = std.ArrayList(Node.Index).init(allocator);
defer ordered_nodes.deinit();
try ordered_nodes.ensureTotalCapacityPrecise(self.nodes.items(.is_terminal).len);
var fifo = std.fifo.LinearFifo(Node.Index, .Dynamic).init(allocator);
defer fifo.deinit();
try fifo.writeItem(self.root.?);
while (fifo.readItem()) |next_index| {
const edges = &self.nodes.items(.edges)[next_index];
for (edges.items) |edge_index| {
const edge = self.edges.items[edge_index];
try fifo.writeItem(edge.node);
}
ordered_nodes.appendAssumeCapacity(next_index);
}
var more: bool = true;
var size: u32 = 0;
while (more) {
size = 0;
more = false;
for (ordered_nodes.items) |node_index| {
const res = try self.finalizeNode(node_index, size);
size += res.node_size;
if (res.updated) more = true;
}
}
try self.buffer.ensureTotalCapacityPrecise(allocator, size);
for (ordered_nodes.items) |node_index| {
try self.writeNode(node_index, self.buffer.writer(allocator));
}
}
const FinalizeNodeResult = struct {
/// Current size of this node in bytes.
node_size: u32,
/// 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 finalizeNode(self: *Trie, node_index: Node.Index, offset_in_trie: u32) !FinalizeNodeResult {
var stream = std.io.countingWriter(std.io.null_writer);
const writer = stream.writer();
const slice = self.nodes.slice();
var node_size: u32 = 0;
if (slice.items(.is_terminal)[node_index]) {
const export_flags = slice.items(.export_flags)[node_index];
const vmaddr_offset = slice.items(.vmaddr_offset)[node_index];
try leb.writeULEB128(writer, export_flags);
try leb.writeULEB128(writer, 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 (slice.items(.edges)[node_index].items) |edge_index| {
const edge = &self.edges.items[edge_index];
const next_node_offset = slice.items(.trie_offset)[edge.node];
node_size += @intCast(edge.label.len + 1);
try leb.writeULEB128(writer, next_node_offset);
}
const trie_offset = slice.items(.trie_offset)[node_index];
const updated = offset_in_trie != trie_offset;
slice.items(.trie_offset)[node_index] = offset_in_trie;
node_size += @intCast(stream.bytes_written);
return .{ .node_size = node_size, .updated = updated };
}
fn init(self: *Trie, allocator: Allocator) !void {
assert(self.root == null);
self.root = try self.addNode(allocator);
}
pub fn deinit(self: *Trie, allocator: Allocator) void {
for (self.nodes.items(.edges)) |*edges| {
edges.deinit(allocator);
}
self.nodes.deinit(allocator);
self.edges.deinit(allocator);
self.buffer.deinit(allocator);
}
pub fn write(self: Trie, writer: anytype) !void {
if (self.buffer.items.len == 0) return;
try writer.writeAll(self.buffer.items);
}
/// 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 writeNode(self: *Trie, node_index: Node.Index, writer: anytype) !void {
const slice = self.nodes.slice();
const edges = slice.items(.edges)[node_index];
const is_terminal = slice.items(.is_terminal)[node_index];
const export_flags = slice.items(.export_flags)[node_index];
const vmaddr_offset = slice.items(.vmaddr_offset)[node_index];
if (is_terminal) {
// 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(export_flags & macho.EXPORT_SYMBOL_FLAGS_REEXPORT == 0 and
export_flags & macho.EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER == 0);
try leb.writeULEB128(info_stream.writer(), export_flags);
try leb.writeULEB128(info_stream.writer(), 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(edges.items.len)));
for (edges.items) |edge_index| {
const edge = self.edges.items[edge_index];
// Write edge label and offset to next node in trie.
try writer.writeAll(edge.label);
try writer.writeByte(0);
try leb.writeULEB128(writer, slice.items(.trie_offset)[edge.node]);
}
}
fn addNode(self: *Trie, allocator: Allocator) !Node.Index {
const index: Node.Index = @intCast(try self.nodes.addOne(allocator));
self.nodes.set(index, .{});
return index;
}
fn addEdge(self: *Trie, allocator: Allocator) !Edge.Index {
const index: Edge.Index = @intCast(self.edges.items.len);
const edge = try self.edges.addOne(allocator);
edge.* = .{};
return index;
}
/// Export symbol that is to be placed in the trie.
pub const ExportSymbol = struct {
@ -305,186 +306,34 @@ pub const ExportSymbol = struct {
export_flags: u64,
};
/// Insert a symbol into the trie, updating the prefixes in the process.
/// This operation may change the layout of the trie by splicing edges in
/// certain circumstances.
pub fn put(self: *Trie, allocator: Allocator, symbol: ExportSymbol) !void {
const node = try self.root.?.put(allocator, symbol.name);
node.terminal_info = .{
.vmaddr_offset = symbol.vmaddr_offset,
.export_flags = symbol.export_flags,
};
self.trie_dirty = true;
}
const Node = struct {
is_terminal: bool = false,
/// Finalizes this trie for writing to a byte stream.
/// This step performs multiple passes through the trie ensuring
/// there are no gaps after every `Node` is ULEB128 encoded.
/// Call this method before trying to `write` the trie to a byte stream.
pub fn finalize(self: *Trie, allocator: Allocator) !void {
if (!self.trie_dirty) return;
/// Export flags associated with this exported symbol.
export_flags: u64 = 0,
self.ordered_nodes.shrinkRetainingCapacity(0);
try self.ordered_nodes.ensureTotalCapacity(allocator, self.node_count);
/// VM address offset wrt to the section this symbol is defined against.
vmaddr_offset: u64 = 0,
var fifo = std.fifo.LinearFifo(*Node, .Dynamic).init(allocator);
defer fifo.deinit();
/// Offset of this node in the trie output byte stream.
trie_offset: u32 = 0,
try fifo.writeItem(self.root.?);
/// List of all edges originating from this node.
edges: std.ArrayListUnmanaged(Edge.Index) = .{},
while (fifo.readItem()) |next| {
for (next.edges.items) |*edge| {
try fifo.writeItem(edge.to);
}
self.ordered_nodes.appendAssumeCapacity(next);
}
var more: bool = true;
while (more) {
self.size = 0;
more = false;
for (self.ordered_nodes.items) |node| {
const res = try node.finalize(self.size);
self.size += res.node_size;
if (res.updated) more = true;
}
}
self.trie_dirty = false;
}
const ReadError = error{
OutOfMemory,
EndOfStream,
Overflow,
const Index = u32;
};
/// Parse the trie from a byte stream.
pub fn read(self: *Trie, allocator: Allocator, reader: anytype) ReadError!usize {
return self.root.?.read(allocator, reader);
}
/// Edge connecting nodes in the trie.
const Edge = struct {
/// Target node in the trie.
node: Node.Index = 0,
/// Write the trie to a byte stream.
/// Panics if the trie was not finalized using `finalize` before calling this method.
pub fn write(self: Trie, writer: anytype) !void {
assert(!self.trie_dirty);
for (self.ordered_nodes.items) |node| {
try node.write(writer);
}
}
/// Matching prefix.
label: []const u8 = "",
pub fn init(self: *Trie, allocator: Allocator) !void {
assert(self.root == null);
const root = try allocator.create(Node);
root.* = .{ .base = self };
self.root = root;
self.node_count += 1;
}
pub fn deinit(self: *Trie, allocator: Allocator) void {
if (self.root) |root| {
root.deinit(allocator);
allocator.destroy(root);
}
self.ordered_nodes.deinit(allocator);
}
test "Trie node count" {
const gpa = testing.allocator;
var trie: Trie = .{};
defer trie.deinit(gpa);
try trie.init(gpa);
try testing.expectEqual(@as(usize, 1), trie.node_count);
try testing.expect(trie.root != null);
try trie.put(gpa, .{
.name = "_main",
.vmaddr_offset = 0,
.export_flags = 0,
});
try testing.expectEqual(@as(usize, 2), trie.node_count);
// Inserting the same node shouldn't update the trie.
try trie.put(gpa, .{
.name = "_main",
.vmaddr_offset = 0,
.export_flags = 0,
});
try testing.expectEqual(@as(usize, 2), trie.node_count);
try trie.put(gpa, .{
.name = "__mh_execute_header",
.vmaddr_offset = 0x1000,
.export_flags = 0,
});
try testing.expectEqual(@as(usize, 4), trie.node_count);
// Inserting the same node shouldn't update the trie.
try trie.put(gpa, .{
.name = "__mh_execute_header",
.vmaddr_offset = 0x1000,
.export_flags = 0,
});
try testing.expectEqual(@as(usize, 4), trie.node_count);
try trie.put(gpa, .{
.name = "_main",
.vmaddr_offset = 0,
.export_flags = 0,
});
try testing.expectEqual(@as(usize, 4), trie.node_count);
}
test "Trie basic" {
const gpa = testing.allocator;
var trie: Trie = .{};
defer trie.deinit(gpa);
try trie.init(gpa);
// root --- _st ---> node
try trie.put(gpa, .{
.name = "_st",
.vmaddr_offset = 0,
.export_flags = 0,
});
try testing.expect(trie.root.?.edges.items.len == 1);
try testing.expect(mem.eql(u8, trie.root.?.edges.items[0].label, "_st"));
{
// root --- _st ---> node --- art ---> node
try trie.put(gpa, .{
.name = "_start",
.vmaddr_offset = 0,
.export_flags = 0,
});
try testing.expect(trie.root.?.edges.items.len == 1);
const nextEdge = &trie.root.?.edges.items[0];
try testing.expect(mem.eql(u8, nextEdge.label, "_st"));
try testing.expect(nextEdge.to.edges.items.len == 1);
try testing.expect(mem.eql(u8, nextEdge.to.edges.items[0].label, "art"));
}
{
// root --- _ ---> node --- st ---> node --- art ---> node
// |
// | --- main ---> node
try trie.put(gpa, .{
.name = "_main",
.vmaddr_offset = 0,
.export_flags = 0,
});
try testing.expect(trie.root.?.edges.items.len == 1);
const nextEdge = &trie.root.?.edges.items[0];
try testing.expect(mem.eql(u8, nextEdge.label, "_"));
try testing.expect(nextEdge.to.edges.items.len == 2);
try testing.expect(mem.eql(u8, nextEdge.to.edges.items[0].label, "st"));
try testing.expect(mem.eql(u8, nextEdge.to.edges.items[1].label, "main"));
const nextNextEdge = &nextEdge.to.edges.items[0];
try testing.expect(mem.eql(u8, nextNextEdge.to.edges.items[0].label, "art"));
}
}
const Index = u32;
};
fn expectEqualHexStrings(expected: []const u8, given: []const u8) !void {
assert(expected.len > 0);
@ -502,7 +351,7 @@ fn expectEqualHexStrings(expected: []const u8, given: []const u8) !void {
}
test "write Trie to a byte stream" {
var gpa = testing.allocator;
const gpa = testing.allocator;
var trie: Trie = .{};
defer trie.deinit(gpa);
try trie.init(gpa);
@ -519,7 +368,6 @@ test "write Trie to a byte stream" {
});
try trie.finalize(gpa);
try trie.finalize(gpa); // Finalizing mulitple times is a nop subsequently unless we add new nodes.
const exp_buffer = [_]u8{
0x0, 0x1, // node root
@ -531,51 +379,7 @@ test "write Trie to a byte stream" {
0x2, 0x0, 0x0, 0x0, // terminal node
0x3, 0x0, 0x80, 0x20, 0x0, // terminal node
};
const buffer = try gpa.alloc(u8, trie.size);
defer gpa.free(buffer);
var stream = std.io.fixedBufferStream(buffer);
{
_ = try trie.write(stream.writer());
try expectEqualHexStrings(&exp_buffer, buffer);
}
{
// Writing finalized trie again should yield the same result.
try stream.seekTo(0);
_ = try trie.write(stream.writer());
try expectEqualHexStrings(&exp_buffer, buffer);
}
}
test "parse Trie from byte stream" {
const gpa = testing.allocator;
const in_buffer = [_]u8{
0x0, 0x1, // node root
0x5f, 0x0, 0x5, // edge '_'
0x0, 0x2, // non-terminal node
0x5f, 0x6d, 0x68, 0x5f, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, // edge '_mh_execute_header'
0x65, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x0, 0x21, // edge '_mh_execute_header'
0x6d, 0x61, 0x69, 0x6e, 0x0, 0x25, // edge 'main'
0x2, 0x0, 0x0, 0x0, // terminal node
0x3, 0x0, 0x80, 0x20, 0x0, // terminal node
};
var in_stream = std.io.fixedBufferStream(&in_buffer);
var trie: Trie = .{};
defer trie.deinit(gpa);
try trie.init(gpa);
const nread = try trie.read(gpa, in_stream.reader());
try testing.expect(nread == in_buffer.len);
try trie.finalize(gpa);
const out_buffer = try gpa.alloc(u8, trie.size);
defer gpa.free(out_buffer);
var out_stream = std.io.fixedBufferStream(out_buffer);
_ = try trie.write(out_stream.writer());
try expectEqualHexStrings(&in_buffer, out_buffer);
try expectEqualHexStrings(&exp_buffer, trie.buffer.items);
}
test "ordering bug" {
@ -602,11 +406,18 @@ test "ordering bug" {
0x88, 0x80, 0x02, 0x01, 0x73, 0x53, 0x74, 0x72,
0x00, 0x12, 0x03, 0x00, 0xD8, 0x0A, 0x00,
};
const buffer = try gpa.alloc(u8, trie.size);
defer gpa.free(buffer);
var stream = std.io.fixedBufferStream(buffer);
// Writing finalized trie again should yield the same result.
_ = try trie.write(stream.writer());
try expectEqualHexStrings(&exp_buffer, buffer);
try expectEqualHexStrings(&exp_buffer, trie.buffer.items);
}
const assert = std.debug.assert;
const leb = std.leb;
const log = std.log.scoped(.macho);
const macho = std.macho;
const mem = std.mem;
const std = @import("std");
const testing = std.testing;
const trace = @import("../../../tracy.zig").trace;
const Allocator = mem.Allocator;
const MachO = @import("../../MachO.zig");
const Trie = @This();

View File

@ -1,14 +1,3 @@
const std = @import("std");
const assert = std.debug.assert;
const leb = std.leb;
const log = std.log.scoped(.link_dyld_info);
const macho = std.macho;
const testing = std.testing;
const Allocator = std.mem.Allocator;
const MachO = @import("../../MachO.zig");
const Symbol = @import("../Symbol.zig");
pub const Entry = struct {
target: Symbol.Index,
offset: u64,
@ -39,11 +28,108 @@ pub const Bind = struct {
self.buffer.deinit(gpa);
}
pub fn size(self: Self) u64 {
return @intCast(self.buffer.items.len);
pub fn updateSize(self: *Self, macho_file: *MachO) !void {
const tracy = trace(@src());
defer tracy.end();
const gpa = macho_file.base.comp.gpa;
const cpu_arch = macho_file.getTarget().cpu.arch;
var objects = try std.ArrayList(File.Index).initCapacity(gpa, macho_file.objects.items.len + 1);
defer objects.deinit();
objects.appendSliceAssumeCapacity(macho_file.objects.items);
if (macho_file.getInternalObject()) |obj| objects.appendAssumeCapacity(obj.index);
for (objects.items) |index| {
const file = macho_file.getFile(index).?;
for (file.getAtoms()) |atom_index| {
const atom = macho_file.getAtom(atom_index) orelse continue;
if (!atom.flags.alive) continue;
if (atom.getInputSection(macho_file).isZerofill()) continue;
const atom_addr = atom.getAddress(macho_file);
const relocs = atom.getRelocs(macho_file);
const seg_id = macho_file.sections.items(.segment_id)[atom.out_n_sect];
const seg = macho_file.segments.items[seg_id];
for (relocs) |rel| {
if (rel.type != .unsigned or rel.meta.length != 3 or rel.tag != .@"extern") continue;
const rel_offset = rel.offset - atom.off;
const addend = rel.addend + rel.getRelocAddend(cpu_arch);
const sym = rel.getTargetSymbol(macho_file);
if (sym.isTlvInit(macho_file)) continue;
const entry = Entry{
.target = rel.target,
.offset = atom_addr + rel_offset - seg.vmaddr,
.segment_id = seg_id,
.addend = addend,
};
if (sym.flags.import or (!(sym.flags.@"export" and sym.flags.weak) and sym.flags.interposable)) {
try self.entries.append(gpa, entry);
}
}
}
}
if (macho_file.got_sect_index) |sid| {
const seg_id = macho_file.sections.items(.segment_id)[sid];
const seg = macho_file.segments.items[seg_id];
for (macho_file.got.symbols.items, 0..) |ref, idx| {
const sym = macho_file.getSymbol(ref);
const addr = macho_file.got.getAddress(@intCast(idx), macho_file);
const entry = Entry{
.target = ref,
.offset = addr - seg.vmaddr,
.segment_id = seg_id,
.addend = 0,
};
if (sym.flags.import or (sym.flags.@"export" and sym.flags.interposable and !sym.flags.weak)) {
try self.entries.append(gpa, entry);
}
}
}
if (macho_file.la_symbol_ptr_sect_index) |sid| {
const sect = macho_file.sections.items(.header)[sid];
const seg_id = macho_file.sections.items(.segment_id)[sid];
const seg = macho_file.segments.items[seg_id];
for (macho_file.stubs.symbols.items, 0..) |ref, idx| {
const sym = macho_file.getSymbol(ref);
const addr = sect.addr + idx * @sizeOf(u64);
const bind_entry = Entry{
.target = ref,
.offset = addr - seg.vmaddr,
.segment_id = seg_id,
.addend = 0,
};
if (sym.flags.import and sym.flags.weak) {
try self.entries.append(gpa, bind_entry);
}
}
}
if (macho_file.tlv_ptr_sect_index) |sid| {
const seg_id = macho_file.sections.items(.segment_id)[sid];
const seg = macho_file.segments.items[seg_id];
for (macho_file.tlv_ptr.symbols.items, 0..) |ref, idx| {
const sym = macho_file.getSymbol(ref);
const addr = macho_file.tlv_ptr.getAddress(@intCast(idx), macho_file);
const entry = Entry{
.target = ref,
.offset = addr - seg.vmaddr,
.segment_id = seg_id,
.addend = 0,
};
if (sym.flags.import or (sym.flags.@"export" and sym.flags.interposable and !sym.flags.weak)) {
try self.entries.append(gpa, entry);
}
}
}
try self.finalize(gpa, macho_file);
macho_file.dyld_info_cmd.bind_size = mem.alignForward(u32, @intCast(self.buffer.items.len), @alignOf(u64));
}
pub fn finalize(self: *Self, gpa: Allocator, ctx: *MachO) !void {
fn finalize(self: *Self, gpa: Allocator, ctx: *MachO) !void {
if (self.entries.items.len == 0) return;
const writer = self.buffer.writer(gpa);
@ -178,7 +264,6 @@ pub const Bind = struct {
}
pub fn write(self: Self, writer: anytype) !void {
if (self.size() == 0) return;
try writer.writeAll(self.buffer.items);
}
};
@ -194,11 +279,109 @@ pub const WeakBind = struct {
self.buffer.deinit(gpa);
}
pub fn size(self: Self) u64 {
return @intCast(self.buffer.items.len);
pub fn updateSize(self: *Self, macho_file: *MachO) !void {
const tracy = trace(@src());
defer tracy.end();
const gpa = macho_file.base.comp.gpa;
const cpu_arch = macho_file.getTarget().cpu.arch;
var objects = try std.ArrayList(File.Index).initCapacity(gpa, macho_file.objects.items.len + 1);
defer objects.deinit();
objects.appendSliceAssumeCapacity(macho_file.objects.items);
if (macho_file.getInternalObject()) |obj| objects.appendAssumeCapacity(obj.index);
for (objects.items) |index| {
const file = macho_file.getFile(index).?;
for (file.getAtoms()) |atom_index| {
const atom = macho_file.getAtom(atom_index) orelse continue;
if (!atom.flags.alive) continue;
if (atom.getInputSection(macho_file).isZerofill()) continue;
const atom_addr = atom.getAddress(macho_file);
const relocs = atom.getRelocs(macho_file);
const seg_id = macho_file.sections.items(.segment_id)[atom.out_n_sect];
const seg = macho_file.segments.items[seg_id];
for (relocs) |rel| {
if (rel.type != .unsigned or rel.meta.length != 3 or rel.tag != .@"extern") continue;
const rel_offset = rel.offset - atom.off;
const addend = rel.addend + rel.getRelocAddend(cpu_arch);
const sym = rel.getTargetSymbol(macho_file);
if (sym.isTlvInit(macho_file)) continue;
const entry = Entry{
.target = rel.target,
.offset = atom_addr + rel_offset - seg.vmaddr,
.segment_id = seg_id,
.addend = addend,
};
if (!sym.isLocal() and sym.flags.weak) {
try self.entries.append(gpa, entry);
}
}
}
}
if (macho_file.got_sect_index) |sid| {
const seg_id = macho_file.sections.items(.segment_id)[sid];
const seg = macho_file.segments.items[seg_id];
for (macho_file.got.symbols.items, 0..) |ref, idx| {
const sym = macho_file.getSymbol(ref);
const addr = macho_file.got.getAddress(@intCast(idx), macho_file);
const entry = Entry{
.target = ref,
.offset = addr - seg.vmaddr,
.segment_id = seg_id,
.addend = 0,
};
if (sym.flags.weak) {
try self.entries.append(gpa, entry);
}
}
}
if (macho_file.la_symbol_ptr_sect_index) |sid| {
const sect = macho_file.sections.items(.header)[sid];
const seg_id = macho_file.sections.items(.segment_id)[sid];
const seg = macho_file.segments.items[seg_id];
for (macho_file.stubs.symbols.items, 0..) |ref, idx| {
const sym = macho_file.getSymbol(ref);
const addr = sect.addr + idx * @sizeOf(u64);
const bind_entry = Entry{
.target = ref,
.offset = addr - seg.vmaddr,
.segment_id = seg_id,
.addend = 0,
};
if (sym.flags.weak) {
try self.entries.append(gpa, bind_entry);
}
}
}
if (macho_file.tlv_ptr_sect_index) |sid| {
const seg_id = macho_file.sections.items(.segment_id)[sid];
const seg = macho_file.segments.items[seg_id];
for (macho_file.tlv_ptr.symbols.items, 0..) |ref, idx| {
const sym = macho_file.getSymbol(ref);
const addr = macho_file.tlv_ptr.getAddress(@intCast(idx), macho_file);
const entry = Entry{
.target = ref,
.offset = addr - seg.vmaddr,
.segment_id = seg_id,
.addend = 0,
};
if (sym.flags.weak) {
try self.entries.append(gpa, entry);
}
}
}
try self.finalize(gpa, macho_file);
macho_file.dyld_info_cmd.weak_bind_size = mem.alignForward(u32, @intCast(self.buffer.items.len), @alignOf(u64));
}
pub fn finalize(self: *Self, gpa: Allocator, ctx: *MachO) !void {
fn finalize(self: *Self, gpa: Allocator, ctx: *MachO) !void {
if (self.entries.items.len == 0) return;
const writer = self.buffer.writer(gpa);
@ -322,7 +505,6 @@ pub const WeakBind = struct {
}
pub fn write(self: Self, writer: anytype) !void {
if (self.size() == 0) return;
try writer.writeAll(self.buffer.items);
}
};
@ -340,11 +522,36 @@ pub const LazyBind = struct {
self.offsets.deinit(gpa);
}
pub fn size(self: Self) u64 {
return @intCast(self.buffer.items.len);
pub fn updateSize(self: *Self, macho_file: *MachO) !void {
const tracy = trace(@src());
defer tracy.end();
const gpa = macho_file.base.comp.gpa;
const sid = macho_file.la_symbol_ptr_sect_index.?;
const sect = macho_file.sections.items(.header)[sid];
const seg_id = macho_file.sections.items(.segment_id)[sid];
const seg = macho_file.segments.items[seg_id];
for (macho_file.stubs.symbols.items, 0..) |ref, idx| {
const sym = macho_file.getSymbol(ref);
const addr = sect.addr + idx * @sizeOf(u64);
const bind_entry = Entry{
.target = ref,
.offset = addr - seg.vmaddr,
.segment_id = seg_id,
.addend = 0,
};
if ((sym.flags.import and !sym.flags.weak) or (sym.flags.interposable and !sym.flags.weak)) {
try self.entries.append(gpa, bind_entry);
}
}
try self.finalize(gpa, macho_file);
macho_file.dyld_info_cmd.lazy_bind_size = mem.alignForward(u32, @intCast(self.buffer.items.len), @alignOf(u64));
}
pub fn finalize(self: *Self, gpa: Allocator, ctx: *MachO) !void {
fn finalize(self: *Self, gpa: Allocator, ctx: *MachO) !void {
try self.offsets.ensureTotalCapacityPrecise(gpa, self.entries.items.len);
const writer = self.buffer.writer(gpa);
@ -474,3 +681,17 @@ fn done(writer: anytype) !void {
log.debug(">>> done", .{});
try writer.writeByte(macho.BIND_OPCODE_DONE);
}
const assert = std.debug.assert;
const leb = std.leb;
const log = std.log.scoped(.link_dyld_info);
const macho = std.macho;
const mem = std.mem;
const testing = std.testing;
const trace = @import("../../../tracy.zig").trace;
const std = @import("std");
const Allocator = mem.Allocator;
const File = @import("../file.zig").File;
const MachO = @import("../../MachO.zig");
const Symbol = @import("../Symbol.zig");

View File

@ -70,22 +70,6 @@ pub const ZigGotSection = struct {
}
}
pub fn addDyldRelocs(zig_got: ZigGotSection, macho_file: *MachO) !void {
const tracy = trace(@src());
defer tracy.end();
const gpa = macho_file.base.comp.gpa;
const seg_id = macho_file.sections.items(.segment_id)[macho_file.zig_got_sect_index.?];
const seg = macho_file.segments.items[seg_id];
for (0..zig_got.entries.items.len) |idx| {
const addr = zig_got.entryAddress(@intCast(idx), macho_file);
try macho_file.rebase.entries.append(gpa, .{
.offset = addr - seg.vmaddr,
.segment_id = seg_id,
});
}
}
const FormatCtx = struct {
zig_got: ZigGotSection,
macho_file: *MachO,
@ -146,41 +130,6 @@ pub const GotSection = struct {
return got.symbols.items.len * @sizeOf(u64);
}
pub fn addDyldRelocs(got: GotSection, macho_file: *MachO) !void {
const tracy = trace(@src());
defer tracy.end();
const gpa = macho_file.base.comp.gpa;
const seg_id = macho_file.sections.items(.segment_id)[macho_file.got_sect_index.?];
const seg = macho_file.segments.items[seg_id];
for (got.symbols.items, 0..) |sym_index, idx| {
const sym = macho_file.getSymbol(sym_index);
const addr = got.getAddress(@intCast(idx), macho_file);
const entry = bind.Entry{
.target = sym_index,
.offset = addr - seg.vmaddr,
.segment_id = seg_id,
.addend = 0,
};
if (sym.flags.import) {
try macho_file.bind.entries.append(gpa, entry);
if (sym.flags.weak) {
try macho_file.weak_bind.entries.append(gpa, entry);
}
} else {
try macho_file.rebase.entries.append(gpa, .{
.offset = addr - seg.vmaddr,
.segment_id = seg_id,
});
if (sym.flags.weak) {
try macho_file.weak_bind.entries.append(gpa, entry);
} else if (sym.flags.interposable) {
try macho_file.bind.entries.append(gpa, entry);
}
}
}
}
pub fn write(got: GotSection, macho_file: *MachO, writer: anytype) !void {
const tracy = trace(@src());
defer tracy.end();
@ -446,49 +395,6 @@ pub const LaSymbolPtrSection = struct {
return macho_file.stubs.symbols.items.len * @sizeOf(u64);
}
pub fn addDyldRelocs(laptr: LaSymbolPtrSection, macho_file: *MachO) !void {
const tracy = trace(@src());
defer tracy.end();
_ = laptr;
const gpa = macho_file.base.comp.gpa;
const sect = macho_file.sections.items(.header)[macho_file.la_symbol_ptr_sect_index.?];
const seg_id = macho_file.sections.items(.segment_id)[macho_file.la_symbol_ptr_sect_index.?];
const seg = macho_file.segments.items[seg_id];
for (macho_file.stubs.symbols.items, 0..) |sym_index, idx| {
const sym = macho_file.getSymbol(sym_index);
const addr = sect.addr + idx * @sizeOf(u64);
const rebase_entry = Rebase.Entry{
.offset = addr - seg.vmaddr,
.segment_id = seg_id,
};
const bind_entry = bind.Entry{
.target = sym_index,
.offset = addr - seg.vmaddr,
.segment_id = seg_id,
.addend = 0,
};
if (sym.flags.import) {
if (sym.flags.weak) {
try macho_file.bind.entries.append(gpa, bind_entry);
try macho_file.weak_bind.entries.append(gpa, bind_entry);
} else {
try macho_file.lazy_bind.entries.append(gpa, bind_entry);
try macho_file.rebase.entries.append(gpa, rebase_entry);
}
} else {
if (sym.flags.weak) {
try macho_file.rebase.entries.append(gpa, rebase_entry);
try macho_file.weak_bind.entries.append(gpa, bind_entry);
} else if (sym.flags.interposable) {
try macho_file.lazy_bind.entries.append(gpa, bind_entry);
try macho_file.rebase.entries.append(gpa, rebase_entry);
}
}
}
}
pub fn write(laptr: LaSymbolPtrSection, macho_file: *MachO, writer: anytype) !void {
const tracy = trace(@src());
defer tracy.end();
@ -539,41 +445,6 @@ pub const TlvPtrSection = struct {
return tlv.symbols.items.len * @sizeOf(u64);
}
pub fn addDyldRelocs(tlv: TlvPtrSection, macho_file: *MachO) !void {
const tracy = trace(@src());
defer tracy.end();
const gpa = macho_file.base.comp.gpa;
const seg_id = macho_file.sections.items(.segment_id)[macho_file.tlv_ptr_sect_index.?];
const seg = macho_file.segments.items[seg_id];
for (tlv.symbols.items, 0..) |sym_index, idx| {
const sym = macho_file.getSymbol(sym_index);
const addr = tlv.getAddress(@intCast(idx), macho_file);
const entry = bind.Entry{
.target = sym_index,
.offset = addr - seg.vmaddr,
.segment_id = seg_id,
.addend = 0,
};
if (sym.flags.import) {
try macho_file.bind.entries.append(gpa, entry);
if (sym.flags.weak) {
try macho_file.weak_bind.entries.append(gpa, entry);
}
} else {
try macho_file.rebase.entries.append(gpa, .{
.offset = addr - seg.vmaddr,
.segment_id = seg_id,
});
if (sym.flags.weak) {
try macho_file.weak_bind.entries.append(gpa, entry);
} else if (sym.flags.interposable) {
try macho_file.bind.entries.append(gpa, entry);
}
}
}
}
pub fn write(tlv: TlvPtrSection, macho_file: *MachO, writer: anytype) !void {
const tracy = trace(@src());
defer tracy.end();
@ -772,21 +643,12 @@ pub const Indsymtab = struct {
}
};
pub const RebaseSection = Rebase;
pub const BindSection = bind.Bind;
pub const WeakBindSection = bind.WeakBind;
pub const LazyBindSection = bind.LazyBind;
pub const ExportTrieSection = Trie;
const aarch64 = @import("../aarch64.zig");
const assert = std.debug.assert;
const bind = @import("dyld_info/bind.zig");
const math = std.math;
const std = @import("std");
const trace = @import("../../tracy.zig").trace;
const Allocator = std.mem.Allocator;
const MachO = @import("../MachO.zig");
const Rebase = @import("dyld_info/Rebase.zig");
const Symbol = @import("Symbol.zig");
const Trie = @import("dyld_info/Trie.zig");