Merge pull request #14369 from ziglang/macho-dyld-ops

macho+zld: add improved dyld opcodes emitters
This commit is contained in:
Jakub Konka 2023-01-19 19:49:51 +01:00 committed by GitHub
commit 989b0e620b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 1469 additions and 455 deletions

View File

@ -594,7 +594,8 @@ 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/ZldAtom.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/bind.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/dyld_info/bind.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/dyld_info/Rebase.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/dead_strip.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/fat.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/load_commands.zig"

View File

@ -14,7 +14,6 @@ const mem = std.mem;
const meta = std.meta;
const aarch64 = @import("../arch/aarch64/bits.zig");
const bind = @import("MachO/bind.zig");
const codegen = @import("../codegen.zig");
const dead_strip = @import("MachO/dead_strip.zig");
const fat = @import("MachO/fat.zig");
@ -50,6 +49,10 @@ const Value = @import("../value.zig").Value;
pub const DebugSymbols = @import("MachO/DebugSymbols.zig");
const Bind = @import("MachO/dyld_info/bind.zig").Bind(*const MachO, MachO.SymbolWithLoc);
const LazyBind = @import("MachO/dyld_info/bind.zig").LazyBind(*const MachO, MachO.SymbolWithLoc);
const Rebase = @import("MachO/dyld_info/Rebase.zig");
pub const base_tag: File.Tag = File.Tag.macho;
pub const SearchStrategy = enum {
@ -3192,32 +3195,14 @@ fn writeLinkeditSegmentData(self: *MachO) !void {
seg.vmsize = mem.alignForwardGeneric(u64, seg.filesize, self.page_size);
}
const AtomLessThanByAddressContext = struct {
macho_file: *MachO,
};
fn atomLessThanByAddress(ctx: AtomLessThanByAddressContext, lhs: *Atom, rhs: *Atom) bool {
return lhs.getSymbol(ctx.macho_file).n_value < rhs.getSymbol(ctx.macho_file).n_value;
}
fn collectRebaseData(self: *MachO, pointers: *std.ArrayList(bind.Pointer)) !void {
fn collectRebaseData(self: *MachO, rebase: *Rebase) !void {
const gpa = self.base.allocator;
var sorted_atoms_by_address = std.ArrayList(*Atom).init(gpa);
defer sorted_atoms_by_address.deinit();
try sorted_atoms_by_address.ensureTotalCapacityPrecise(self.rebases.count());
var it = self.rebases.keyIterator();
while (it.next()) |key_ptr| {
sorted_atoms_by_address.appendAssumeCapacity(key_ptr.*);
}
std.sort.sort(*Atom, sorted_atoms_by_address.items, AtomLessThanByAddressContext{
.macho_file = self,
}, atomLessThanByAddress);
const slice = self.sections.slice();
for (sorted_atoms_by_address.items) |atom| {
var it = self.rebases.keyIterator();
while (it.next()) |key_ptr| {
const atom = key_ptr.*;
log.debug(" ATOM(%{d}, '{s}')", .{ atom.sym_index, atom.getName(self) });
const sym = atom.getSymbol(self);
@ -3227,36 +3212,29 @@ fn collectRebaseData(self: *MachO, pointers: *std.ArrayList(bind.Pointer)) !void
const base_offset = sym.n_value - seg.vmaddr;
const rebases = self.rebases.get(atom).?;
try pointers.ensureUnusedCapacity(rebases.items.len);
try rebase.entries.ensureUnusedCapacity(gpa, rebases.items.len);
for (rebases.items) |offset| {
log.debug(" | rebase at {x}", .{base_offset + offset});
pointers.appendAssumeCapacity(.{
rebase.entries.appendAssumeCapacity(.{
.offset = base_offset + offset,
.segment_id = segment_index,
});
}
}
try rebase.finalize(gpa);
}
fn collectBindData(self: *MachO, pointers: *std.ArrayList(bind.Pointer), raw_bindings: anytype) !void {
fn collectBindData(self: *MachO, bind: anytype, raw_bindings: anytype) !void {
const gpa = self.base.allocator;
var sorted_atoms_by_address = std.ArrayList(*Atom).init(gpa);
defer sorted_atoms_by_address.deinit();
try sorted_atoms_by_address.ensureTotalCapacityPrecise(raw_bindings.count());
var it = raw_bindings.keyIterator();
while (it.next()) |key_ptr| {
sorted_atoms_by_address.appendAssumeCapacity(key_ptr.*);
}
std.sort.sort(*Atom, sorted_atoms_by_address.items, AtomLessThanByAddressContext{
.macho_file = self,
}, atomLessThanByAddress);
const slice = self.sections.slice();
for (sorted_atoms_by_address.items) |atom| {
var it = raw_bindings.keyIterator();
while (it.next()) |key_ptr| {
const atom = key_ptr.*;
log.debug(" ATOM(%{d}, '{s}')", .{ atom.sym_index, atom.getName(self) });
const sym = atom.getSymbol(self);
@ -3266,7 +3244,8 @@ fn collectBindData(self: *MachO, pointers: *std.ArrayList(bind.Pointer), raw_bin
const base_offset = sym.n_value - seg.vmaddr;
const bindings = raw_bindings.get(atom).?;
try pointers.ensureUnusedCapacity(bindings.items.len);
try bind.entries.ensureUnusedCapacity(gpa, bindings.items.len);
for (bindings.items) |binding| {
const bind_sym = self.getSymbol(binding.target);
const bind_sym_name = self.getSymbolName(binding.target);
@ -3274,7 +3253,6 @@ fn collectBindData(self: *MachO, pointers: *std.ArrayList(bind.Pointer), raw_bin
@bitCast(i16, bind_sym.n_desc),
macho.N_SYMBOL_RESOLVER,
);
var flags: u4 = 0;
log.debug(" | bind at {x}, import('{s}') in dylib({d})", .{
binding.offset + base_offset,
bind_sym_name,
@ -3282,17 +3260,17 @@ fn collectBindData(self: *MachO, pointers: *std.ArrayList(bind.Pointer), raw_bin
});
if (bind_sym.weakRef()) {
log.debug(" | marking as weak ref ", .{});
flags |= @truncate(u4, macho.BIND_SYMBOL_FLAGS_WEAK_IMPORT);
}
pointers.appendAssumeCapacity(.{
bind.entries.appendAssumeCapacity(.{
.target = binding.target,
.offset = binding.offset + base_offset,
.segment_id = segment_index,
.dylib_ordinal = dylib_ordinal,
.name = bind_sym_name,
.bind_flags = flags,
.addend = 0,
});
}
}
try bind.finalize(gpa, self);
}
fn collectExportData(self: *MachO, trie: *Trie) !void {
@ -3345,17 +3323,17 @@ fn writeDyldInfoData(self: *MachO) !void {
const gpa = self.base.allocator;
var rebase_pointers = std.ArrayList(bind.Pointer).init(gpa);
defer rebase_pointers.deinit();
try self.collectRebaseData(&rebase_pointers);
var rebase = Rebase{};
defer rebase.deinit(gpa);
try self.collectRebaseData(&rebase);
var bind_pointers = std.ArrayList(bind.Pointer).init(gpa);
defer bind_pointers.deinit();
try self.collectBindData(&bind_pointers, self.bindings);
var bind = Bind{};
defer bind.deinit(gpa);
try self.collectBindData(&bind, self.bindings);
var lazy_bind_pointers = std.ArrayList(bind.Pointer).init(gpa);
defer lazy_bind_pointers.deinit();
try self.collectBindData(&lazy_bind_pointers, self.lazy_bindings);
var lazy_bind = LazyBind{};
defer lazy_bind.deinit(gpa);
try self.collectBindData(&lazy_bind, self.lazy_bindings);
var trie: Trie = .{};
defer trie.deinit(gpa);
@ -3364,17 +3342,17 @@ fn writeDyldInfoData(self: *MachO) !void {
const link_seg = self.getLinkeditSegmentPtr();
assert(mem.isAlignedGeneric(u64, link_seg.fileoff, @alignOf(u64)));
const rebase_off = link_seg.fileoff;
const rebase_size = try bind.rebaseInfoSize(rebase_pointers.items);
const rebase_size = rebase.size();
const rebase_size_aligned = mem.alignForwardGeneric(u64, rebase_size, @alignOf(u64));
log.debug("writing rebase info from 0x{x} to 0x{x}", .{ rebase_off, rebase_off + rebase_size_aligned });
const bind_off = rebase_off + rebase_size_aligned;
const bind_size = try bind.bindInfoSize(bind_pointers.items);
const bind_size = bind.size();
const bind_size_aligned = mem.alignForwardGeneric(u64, bind_size, @alignOf(u64));
log.debug("writing bind info from 0x{x} to 0x{x}", .{ bind_off, bind_off + bind_size_aligned });
const lazy_bind_off = bind_off + bind_size_aligned;
const lazy_bind_size = try bind.lazyBindInfoSize(lazy_bind_pointers.items);
const lazy_bind_size = lazy_bind.size();
const lazy_bind_size_aligned = mem.alignForwardGeneric(u64, lazy_bind_size, @alignOf(u64));
log.debug("writing lazy bind info from 0x{x} to 0x{x}", .{
lazy_bind_off,
@ -3398,13 +3376,13 @@ fn writeDyldInfoData(self: *MachO) !void {
var stream = std.io.fixedBufferStream(buffer);
const writer = stream.writer();
try bind.writeRebaseInfo(rebase_pointers.items, writer);
try rebase.write(writer);
try stream.seekTo(bind_off - rebase_off);
try bind.writeBindInfo(bind_pointers.items, writer);
try bind.write(writer);
try stream.seekTo(lazy_bind_off - rebase_off);
try bind.writeLazyBindInfo(lazy_bind_pointers.items, writer);
try lazy_bind.write(writer);
try stream.seekTo(export_off - rebase_off);
_ = try trie.write(writer);
@ -3415,9 +3393,7 @@ fn writeDyldInfoData(self: *MachO) !void {
});
try self.base.file.?.pwriteAll(buffer, rebase_off);
const start = math.cast(usize, lazy_bind_off - rebase_off) orelse return error.Overflow;
const end = start + (math.cast(usize, lazy_bind_size) orelse return error.Overflow);
try self.populateLazyBindOffsetsInStubHelper(buffer[start..end]);
try self.populateLazyBindOffsetsInStubHelper(lazy_bind);
self.dyld_info_cmd.rebase_off = @intCast(u32, rebase_off);
self.dyld_info_cmd.rebase_size = @intCast(u32, rebase_size_aligned);
@ -3429,102 +3405,33 @@ fn writeDyldInfoData(self: *MachO) !void {
self.dyld_info_cmd.export_size = @intCast(u32, export_size_aligned);
}
fn populateLazyBindOffsetsInStubHelper(self: *MachO, buffer: []const u8) !void {
const gpa = self.base.allocator;
fn populateLazyBindOffsetsInStubHelper(self: *MachO, lazy_bind: LazyBind) !void {
if (lazy_bind.size() == 0) return;
const stub_helper_section_index = self.stub_helper_section_index orelse return;
if (self.stub_helper_preamble_atom == null) return;
const stub_helper_section_index = self.stub_helper_section_index.?;
assert(self.stub_helper_preamble_atom != null);
const section = self.sections.get(stub_helper_section_index);
const last_atom = section.last_atom orelse return;
if (last_atom == self.stub_helper_preamble_atom.?) return; // TODO is this a redundant check?
var table = std.AutoHashMap(i64, *Atom).init(gpa);
defer table.deinit();
{
var stub_atom = last_atom;
var laptr_atom = self.sections.items(.last_atom)[self.la_symbol_ptr_section_index.?].?;
const base_addr = self.getSegment(self.la_symbol_ptr_section_index.?).vmaddr;
while (true) {
const laptr_off = blk: {
const sym = laptr_atom.getSymbol(self);
break :blk @intCast(i64, sym.n_value - base_addr);
};
try table.putNoClobber(laptr_off, stub_atom);
if (laptr_atom.prev) |prev| {
laptr_atom = prev;
stub_atom = stub_atom.prev.?;
} else break;
}
}
var stream = std.io.fixedBufferStream(buffer);
var reader = stream.reader();
var offsets = std.ArrayList(struct { sym_offset: i64, offset: u32 }).init(gpa);
try offsets.append(.{ .sym_offset = undefined, .offset = 0 });
defer offsets.deinit();
var valid_block = false;
while (true) {
const inst = reader.readByte() catch |err| switch (err) {
error.EndOfStream => break,
};
const opcode: u8 = inst & macho.BIND_OPCODE_MASK;
switch (opcode) {
macho.BIND_OPCODE_DO_BIND => {
valid_block = true;
},
macho.BIND_OPCODE_DONE => {
if (valid_block) {
const offset = try stream.getPos();
try offsets.append(.{ .sym_offset = undefined, .offset = @intCast(u32, offset) });
}
valid_block = false;
},
macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM => {
var next = try reader.readByte();
while (next != @as(u8, 0)) {
next = try reader.readByte();
}
},
macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB => {
var inserted = offsets.pop();
inserted.sym_offset = try std.leb.readILEB128(i64, reader);
try offsets.append(inserted);
},
macho.BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB => {
_ = try std.leb.readULEB128(u64, reader);
},
macho.BIND_OPCODE_SET_ADDEND_SLEB => {
_ = try std.leb.readILEB128(i64, reader);
},
else => {},
}
}
const header = self.sections.items(.header)[stub_helper_section_index];
const stub_offset: u4 = switch (self.base.options.target.cpu.arch) {
.x86_64 => 1,
.aarch64 => 2 * @sizeOf(u32),
else => unreachable,
};
var buf: [@sizeOf(u32)]u8 = undefined;
_ = offsets.pop();
const header = section.header;
var atom = section.last_atom.?;
while (offsets.popOrNull()) |bind_offset| {
const atom = table.get(bind_offset.sym_offset).?;
var index: usize = 0;
while (index < lazy_bind.offsets.items.len) : (index += 1) {
const sym = atom.getSymbol(self);
const file_offset = header.offset + sym.n_value - header.addr + stub_offset;
mem.writeIntLittle(u32, &buf, bind_offset.offset);
log.debug("writing lazy bind offset in stub helper of 0x{x} for symbol {s} at offset 0x{x}", .{
bind_offset.offset,
atom.getName(self),
file_offset,
});
try self.base.file.?.pwriteAll(&buf, file_offset);
const bind_offset = lazy_bind.offsets.items[index];
log.debug("writing lazy bind offset 0x{x} in stub helper at 0x{x}", .{ bind_offset, file_offset });
try self.base.file.?.pwriteAll(mem.asBytes(&bind_offset), file_offset);
atom = atom.prev.?;
}
}
@ -3912,12 +3819,13 @@ pub fn getSymbolPtr(self: *MachO, sym_with_loc: SymbolWithLoc) *macho.nlist_64 {
}
/// Returns symbol described by `sym_with_loc` descriptor.
pub fn getSymbol(self: *MachO, sym_with_loc: SymbolWithLoc) macho.nlist_64 {
return self.getSymbolPtr(sym_with_loc).*;
pub fn getSymbol(self: *const MachO, sym_with_loc: SymbolWithLoc) macho.nlist_64 {
assert(sym_with_loc.file == null);
return self.locals.items[sym_with_loc.sym_index];
}
/// Returns name of the symbol described by `sym_with_loc` descriptor.
pub fn getSymbolName(self: *MachO, sym_with_loc: SymbolWithLoc) []const u8 {
pub fn getSymbolName(self: *const MachO, sym_with_loc: SymbolWithLoc) []const u8 {
assert(sym_with_loc.file == null);
const sym = self.locals.items[sym_with_loc.sym_index];
return self.strtab.get(sym.n_strx).?;

View File

@ -1,138 +0,0 @@
const std = @import("std");
const leb = std.leb;
const macho = std.macho;
pub const Pointer = struct {
offset: u64,
segment_id: u16,
dylib_ordinal: ?i64 = null,
name: ?[]const u8 = null,
bind_flags: u4 = 0,
};
pub fn rebaseInfoSize(pointers: []const Pointer) !u64 {
var stream = std.io.countingWriter(std.io.null_writer);
var writer = stream.writer();
var size: u64 = 0;
for (pointers) |pointer| {
size += 2;
try leb.writeILEB128(writer, pointer.offset);
size += 1;
}
size += 1 + stream.bytes_written;
return size;
}
pub fn writeRebaseInfo(pointers: []const Pointer, writer: anytype) !void {
for (pointers) |pointer| {
try writer.writeByte(macho.REBASE_OPCODE_SET_TYPE_IMM | @truncate(u4, macho.REBASE_TYPE_POINTER));
try writer.writeByte(macho.REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | @truncate(u4, pointer.segment_id));
try leb.writeILEB128(writer, pointer.offset);
try writer.writeByte(macho.REBASE_OPCODE_DO_REBASE_IMM_TIMES | @truncate(u4, 1));
}
try writer.writeByte(macho.REBASE_OPCODE_DONE);
}
pub fn bindInfoSize(pointers: []const Pointer) !u64 {
var stream = std.io.countingWriter(std.io.null_writer);
var writer = stream.writer();
var size: u64 = 0;
for (pointers) |pointer| {
size += 1;
if (pointer.dylib_ordinal.? > 15) {
try leb.writeULEB128(writer, @bitCast(u64, pointer.dylib_ordinal.?));
}
size += 1;
size += 1;
size += pointer.name.?.len;
size += 1;
size += 1;
try leb.writeILEB128(writer, pointer.offset);
size += 1;
}
size += stream.bytes_written + 1;
return size;
}
pub fn writeBindInfo(pointers: []const Pointer, writer: anytype) !void {
for (pointers) |pointer| {
if (pointer.dylib_ordinal.? > 15) {
try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB);
try leb.writeULEB128(writer, @bitCast(u64, pointer.dylib_ordinal.?));
} else if (pointer.dylib_ordinal.? > 0) {
try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | @truncate(u4, @bitCast(u64, pointer.dylib_ordinal.?)));
} else {
try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_SPECIAL_IMM | @truncate(u4, @bitCast(u64, pointer.dylib_ordinal.?)));
}
try writer.writeByte(macho.BIND_OPCODE_SET_TYPE_IMM | @truncate(u4, macho.BIND_TYPE_POINTER));
try writer.writeByte(macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | pointer.bind_flags);
try writer.writeAll(pointer.name.?);
try writer.writeByte(0);
try writer.writeByte(macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | @truncate(u4, pointer.segment_id));
try leb.writeILEB128(writer, pointer.offset);
try writer.writeByte(macho.BIND_OPCODE_DO_BIND);
}
try writer.writeByte(macho.BIND_OPCODE_DONE);
}
pub fn lazyBindInfoSize(pointers: []const Pointer) !u64 {
var stream = std.io.countingWriter(std.io.null_writer);
var writer = stream.writer();
var size: u64 = 0;
for (pointers) |pointer| {
size += 1;
try leb.writeILEB128(writer, pointer.offset);
size += 1;
if (pointer.dylib_ordinal.? > 15) {
try leb.writeULEB128(writer, @bitCast(u64, pointer.dylib_ordinal.?));
}
size += 1;
size += pointer.name.?.len;
size += 1;
size += 2;
}
size += stream.bytes_written;
return size;
}
pub fn writeLazyBindInfo(pointers: []const Pointer, writer: anytype) !void {
for (pointers) |pointer| {
try writer.writeByte(macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | @truncate(u4, pointer.segment_id));
try leb.writeILEB128(writer, pointer.offset);
if (pointer.dylib_ordinal.? > 15) {
try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB);
try leb.writeULEB128(writer, @bitCast(u64, pointer.dylib_ordinal.?));
} else if (pointer.dylib_ordinal.? > 0) {
try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | @truncate(u4, @bitCast(u64, pointer.dylib_ordinal.?)));
} else {
try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_SPECIAL_IMM | @truncate(u4, @bitCast(u64, pointer.dylib_ordinal.?)));
}
try writer.writeByte(macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | pointer.bind_flags);
try writer.writeAll(pointer.name.?);
try writer.writeByte(0);
try writer.writeByte(macho.BIND_OPCODE_DO_BIND);
try writer.writeByte(macho.BIND_OPCODE_DONE);
}
}

View File

@ -0,0 +1,574 @@
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) = .{},
const Entry = struct {
offset: u64,
segment_id: u8,
pub fn lessThan(ctx: void, entry: Entry, other: Entry) bool {
_ = ctx;
if (entry.segment_id == other.segment_id) {
return entry.offset < other.offset;
}
return entry.segment_id < other.segment_id;
}
};
pub fn deinit(rebase: *Rebase, gpa: Allocator) void {
rebase.entries.deinit(gpa);
rebase.buffer.deinit(gpa);
}
pub fn size(rebase: Rebase) u64 {
return @intCast(u64, rebase.buffer.items.len);
}
pub fn finalize(rebase: *Rebase, gpa: Allocator) !void {
if (rebase.entries.items.len == 0) return;
const writer = rebase.buffer.writer(gpa);
std.sort.sort(Entry, rebase.entries.items, {}, Entry.lessThan);
try setTypePointer(writer);
var start: usize = 0;
var seg_id: ?u8 = null;
for (rebase.entries.items) |entry, i| {
if (seg_id != null and seg_id.? == entry.segment_id) continue;
try finalizeSegment(rebase.entries.items[start..i], writer);
seg_id = entry.segment_id;
start = i;
}
try finalizeSegment(rebase.entries.items[start..], writer);
try done(writer);
}
fn finalizeSegment(entries: []const Entry, writer: anytype) !void {
if (entries.len == 0) return;
const segment_id = entries[0].segment_id;
var offset = entries[0].offset;
try setSegmentOffset(segment_id, offset, writer);
var count: usize = 0;
var skip: u64 = 0;
var state: enum {
start,
times,
times_skip,
} = .times;
var i: usize = 0;
while (i < entries.len) : (i += 1) {
log.debug("{x}, {d}, {x}, {s}", .{ offset, count, skip, @tagName(state) });
const current_offset = entries[i].offset;
log.debug(" => {x}", .{current_offset});
switch (state) {
.start => {
if (offset < current_offset) {
const delta = current_offset - offset;
try addAddr(delta, writer);
offset += delta;
}
state = .times;
offset += @sizeOf(u64);
count = 1;
},
.times => {
const delta = current_offset - offset;
if (delta == 0) {
count += 1;
offset += @sizeOf(u64);
continue;
}
if (count == 1) {
state = .times_skip;
skip = delta;
offset += skip;
i -= 1;
} else {
try rebaseTimes(count, writer);
state = .start;
i -= 1;
}
},
.times_skip => {
if (current_offset < offset) {
count -= 1;
if (count == 1) {
try rebaseAddAddr(skip, writer);
} else {
try rebaseTimesSkip(count, skip, writer);
}
state = .start;
offset = offset - (@sizeOf(u64) + skip);
i -= 2;
continue;
}
const delta = current_offset - offset;
if (delta == 0) {
count += 1;
offset += @sizeOf(u64) + skip;
} else {
try rebaseTimesSkip(count, skip, writer);
state = .start;
i -= 1;
}
},
}
}
switch (state) {
.start => unreachable,
.times => {
try rebaseTimes(count, writer);
},
.times_skip => {
try rebaseTimesSkip(count, skip, writer);
},
}
}
fn setTypePointer(writer: anytype) !void {
log.debug(">>> set type: {d}", .{macho.REBASE_TYPE_POINTER});
try writer.writeByte(macho.REBASE_OPCODE_SET_TYPE_IMM | @truncate(u4, macho.REBASE_TYPE_POINTER));
}
fn setSegmentOffset(segment_id: u8, offset: u64, writer: anytype) !void {
log.debug(">>> set segment: {d} and offset: {x}", .{ segment_id, offset });
try writer.writeByte(macho.REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | @truncate(u4, segment_id));
try std.leb.writeULEB128(writer, offset);
}
fn rebaseAddAddr(addr: u64, writer: anytype) !void {
log.debug(">>> rebase with add: {x}", .{addr});
try writer.writeByte(macho.REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB);
try std.leb.writeULEB128(writer, addr);
}
fn rebaseTimes(count: usize, writer: anytype) !void {
log.debug(">>> rebase with count: {d}", .{count});
if (count <= 0xf) {
try writer.writeByte(macho.REBASE_OPCODE_DO_REBASE_IMM_TIMES | @truncate(u4, count));
} else {
try writer.writeByte(macho.REBASE_OPCODE_DO_REBASE_ULEB_TIMES);
try std.leb.writeULEB128(writer, count);
}
}
fn rebaseTimesSkip(count: usize, skip: u64, writer: anytype) !void {
log.debug(">>> rebase with count: {d} and skip: {x}", .{ count, skip });
try writer.writeByte(macho.REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB);
try std.leb.writeULEB128(writer, count);
try std.leb.writeULEB128(writer, skip);
}
fn addAddr(addr: u64, writer: anytype) !void {
log.debug(">>> add: {x}", .{addr});
if (std.mem.isAlignedGeneric(u64, addr, @sizeOf(u64))) {
const imm = @divExact(addr, @sizeOf(u64));
if (imm <= 0xf) {
try writer.writeByte(macho.REBASE_OPCODE_ADD_ADDR_IMM_SCALED | @truncate(u4, imm));
return;
}
}
try writer.writeByte(macho.REBASE_OPCODE_ADD_ADDR_ULEB);
try std.leb.writeULEB128(writer, addr);
}
fn done(writer: anytype) !void {
log.debug(">>> done", .{});
try writer.writeByte(macho.REBASE_OPCODE_DONE);
}
pub fn write(rebase: Rebase, writer: anytype) !void {
if (rebase.size() == 0) return;
try writer.writeAll(rebase.buffer.items);
}
test "rebase - no entries" {
const gpa = testing.allocator;
var rebase = Rebase{};
defer rebase.deinit(gpa);
try rebase.finalize(gpa);
try testing.expectEqual(@as(u64, 0), rebase.size());
}
test "rebase - single entry" {
const gpa = testing.allocator;
var rebase = Rebase{};
defer rebase.deinit(gpa);
try rebase.entries.append(gpa, .{
.segment_id = 1,
.offset = 0x10,
});
try rebase.finalize(gpa);
try testing.expectEqualSlices(u8, &[_]u8{
macho.REBASE_OPCODE_SET_TYPE_IMM | macho.REBASE_TYPE_POINTER,
macho.REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 1,
0x10,
macho.REBASE_OPCODE_DO_REBASE_IMM_TIMES | 1,
macho.REBASE_OPCODE_DONE,
}, rebase.buffer.items);
}
test "rebase - emitTimes - IMM" {
const gpa = testing.allocator;
var rebase = Rebase{};
defer rebase.deinit(gpa);
var i: u64 = 0;
while (i < 10) : (i += 1) {
try rebase.entries.append(gpa, .{
.segment_id = 1,
.offset = i * @sizeOf(u64),
});
}
try rebase.finalize(gpa);
try testing.expectEqualSlices(u8, &[_]u8{
macho.REBASE_OPCODE_SET_TYPE_IMM | macho.REBASE_TYPE_POINTER,
macho.REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 1,
0x0,
macho.REBASE_OPCODE_DO_REBASE_IMM_TIMES | 10,
macho.REBASE_OPCODE_DONE,
}, rebase.buffer.items);
}
test "rebase - emitTimes - ULEB" {
const gpa = testing.allocator;
var rebase = Rebase{};
defer rebase.deinit(gpa);
var i: u64 = 0;
while (i < 100) : (i += 1) {
try rebase.entries.append(gpa, .{
.segment_id = 1,
.offset = i * @sizeOf(u64),
});
}
try rebase.finalize(gpa);
try testing.expectEqualSlices(u8, &[_]u8{
macho.REBASE_OPCODE_SET_TYPE_IMM | macho.REBASE_TYPE_POINTER,
macho.REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 1,
0x0,
macho.REBASE_OPCODE_DO_REBASE_ULEB_TIMES,
0x64,
macho.REBASE_OPCODE_DONE,
}, rebase.buffer.items);
}
test "rebase - emitTimes followed by addAddr followed by emitTimes" {
const gpa = testing.allocator;
var rebase = Rebase{};
defer rebase.deinit(gpa);
var offset: u64 = 0;
var i: u64 = 0;
while (i < 15) : (i += 1) {
try rebase.entries.append(gpa, .{
.segment_id = 1,
.offset = offset,
});
offset += @sizeOf(u64);
}
offset += @sizeOf(u64);
try rebase.entries.append(gpa, .{
.segment_id = 1,
.offset = offset,
});
try rebase.finalize(gpa);
try testing.expectEqualSlices(u8, &[_]u8{
macho.REBASE_OPCODE_SET_TYPE_IMM | macho.REBASE_TYPE_POINTER,
macho.REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 1,
0x0,
macho.REBASE_OPCODE_DO_REBASE_IMM_TIMES | 15,
macho.REBASE_OPCODE_ADD_ADDR_IMM_SCALED | 1,
macho.REBASE_OPCODE_DO_REBASE_IMM_TIMES | 1,
macho.REBASE_OPCODE_DONE,
}, rebase.buffer.items);
}
test "rebase - emitTimesSkip" {
const gpa = testing.allocator;
var rebase = Rebase{};
defer rebase.deinit(gpa);
var offset: u64 = 0;
var i: u64 = 0;
while (i < 15) : (i += 1) {
try rebase.entries.append(gpa, .{
.segment_id = 1,
.offset = offset,
});
offset += 2 * @sizeOf(u64);
}
try rebase.finalize(gpa);
try testing.expectEqualSlices(u8, &[_]u8{
macho.REBASE_OPCODE_SET_TYPE_IMM | macho.REBASE_TYPE_POINTER,
macho.REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 1,
0x0,
macho.REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB,
0xf,
0x8,
macho.REBASE_OPCODE_DONE,
}, rebase.buffer.items);
}
test "rebase - complex" {
const gpa = testing.allocator;
var rebase = Rebase{};
defer rebase.deinit(gpa);
try rebase.entries.append(gpa, .{
.segment_id = 1,
.offset = 0,
});
try rebase.entries.append(gpa, .{
.segment_id = 1,
.offset = 0x10,
});
try rebase.entries.append(gpa, .{
.segment_id = 1,
.offset = 0x40,
});
try rebase.entries.append(gpa, .{
.segment_id = 1,
.offset = 0x48,
});
try rebase.entries.append(gpa, .{
.segment_id = 1,
.offset = 0x50,
});
try rebase.entries.append(gpa, .{
.segment_id = 1,
.offset = 0x58,
});
try rebase.entries.append(gpa, .{
.segment_id = 1,
.offset = 0x70,
});
try rebase.finalize(gpa);
try testing.expectEqualSlices(u8, &[_]u8{
macho.REBASE_OPCODE_SET_TYPE_IMM | macho.REBASE_TYPE_POINTER,
macho.REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 1,
0x0,
macho.REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB,
0x2,
0x8,
macho.REBASE_OPCODE_ADD_ADDR_IMM_SCALED | 4,
macho.REBASE_OPCODE_DO_REBASE_IMM_TIMES | 4,
macho.REBASE_OPCODE_ADD_ADDR_IMM_SCALED | 2,
macho.REBASE_OPCODE_DO_REBASE_IMM_TIMES | 1,
macho.REBASE_OPCODE_DONE,
}, rebase.buffer.items);
}
test "rebase - complex 2" {
const gpa = testing.allocator;
var rebase = Rebase{};
defer rebase.deinit(gpa);
try rebase.entries.append(gpa, .{
.segment_id = 1,
.offset = 0,
});
try rebase.entries.append(gpa, .{
.segment_id = 1,
.offset = 0x10,
});
try rebase.entries.append(gpa, .{
.segment_id = 1,
.offset = 0x28,
});
try rebase.entries.append(gpa, .{
.segment_id = 1,
.offset = 0x48,
});
try rebase.entries.append(gpa, .{
.segment_id = 1,
.offset = 0x78,
});
try rebase.entries.append(gpa, .{
.segment_id = 1,
.offset = 0xb8,
});
try rebase.entries.append(gpa, .{
.segment_id = 2,
.offset = 0x0,
});
try rebase.entries.append(gpa, .{
.segment_id = 2,
.offset = 0x8,
});
try rebase.entries.append(gpa, .{
.segment_id = 2,
.offset = 0x10,
});
try rebase.entries.append(gpa, .{
.segment_id = 2,
.offset = 0x18,
});
try rebase.entries.append(gpa, .{
.segment_id = 3,
.offset = 0x0,
});
try rebase.entries.append(gpa, .{
.segment_id = 3,
.offset = 0x20,
});
try rebase.entries.append(gpa, .{
.segment_id = 3,
.offset = 0x40,
});
try rebase.entries.append(gpa, .{
.segment_id = 3,
.offset = 0x60,
});
try rebase.entries.append(gpa, .{
.segment_id = 3,
.offset = 0x68,
});
try rebase.finalize(gpa);
try testing.expectEqualSlices(u8, &[_]u8{
macho.REBASE_OPCODE_SET_TYPE_IMM | macho.REBASE_TYPE_POINTER,
macho.REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 1,
0x0,
macho.REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB,
0x2,
0x8,
macho.REBASE_OPCODE_ADD_ADDR_IMM_SCALED | 1,
macho.REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB,
0x2,
0x18,
macho.REBASE_OPCODE_ADD_ADDR_IMM_SCALED | 2,
macho.REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB,
0x2,
0x38,
macho.REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 2,
0x0,
macho.REBASE_OPCODE_DO_REBASE_IMM_TIMES | 4,
macho.REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 3,
0x0,
macho.REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB,
0x3,
0x18,
macho.REBASE_OPCODE_DO_REBASE_IMM_TIMES | 2,
macho.REBASE_OPCODE_DONE,
}, rebase.buffer.items);
}
test "rebase - composite" {
const gpa = testing.allocator;
var rebase = Rebase{};
defer rebase.deinit(gpa);
try rebase.entries.append(gpa, .{
.segment_id = 1,
.offset = 0x8,
});
try rebase.entries.append(gpa, .{
.segment_id = 1,
.offset = 0x38,
});
try rebase.entries.append(gpa, .{
.segment_id = 1,
.offset = 0xa0,
});
try rebase.entries.append(gpa, .{
.segment_id = 1,
.offset = 0xa8,
});
try rebase.entries.append(gpa, .{
.segment_id = 1,
.offset = 0xb0,
});
try rebase.entries.append(gpa, .{
.segment_id = 1,
.offset = 0xc0,
});
try rebase.entries.append(gpa, .{
.segment_id = 1,
.offset = 0xc8,
});
try rebase.entries.append(gpa, .{
.segment_id = 1,
.offset = 0xd0,
});
try rebase.entries.append(gpa, .{
.segment_id = 1,
.offset = 0xd8,
});
try rebase.entries.append(gpa, .{
.segment_id = 1,
.offset = 0xe0,
});
try rebase.entries.append(gpa, .{
.segment_id = 1,
.offset = 0xe8,
});
try rebase.entries.append(gpa, .{
.segment_id = 1,
.offset = 0xf0,
});
try rebase.entries.append(gpa, .{
.segment_id = 1,
.offset = 0xf8,
});
try rebase.entries.append(gpa, .{
.segment_id = 1,
.offset = 0x108,
});
try rebase.finalize(gpa);
try testing.expectEqualSlices(u8, &[_]u8{
macho.REBASE_OPCODE_SET_TYPE_IMM | macho.REBASE_TYPE_POINTER,
macho.REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 1,
0x8,
macho.REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB,
0x2,
0x28,
macho.REBASE_OPCODE_ADD_ADDR_IMM_SCALED | 7,
macho.REBASE_OPCODE_DO_REBASE_IMM_TIMES | 3,
macho.REBASE_OPCODE_ADD_ADDR_IMM_SCALED | 1,
macho.REBASE_OPCODE_DO_REBASE_IMM_TIMES | 8,
macho.REBASE_OPCODE_ADD_ADDR_IMM_SCALED | 1,
macho.REBASE_OPCODE_DO_REBASE_IMM_TIMES | 1,
macho.REBASE_OPCODE_DONE,
}, rebase.buffer.items);
}

View File

@ -0,0 +1,740 @@
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) = .{},
buffer: std.ArrayListUnmanaged(u8) = .{},
const Self = @This();
const Entry = struct {
target: Target,
offset: u64,
segment_id: u8,
addend: i64,
pub fn lessThan(ctx: Ctx, entry: Entry, other: Entry) bool {
if (entry.segment_id == other.segment_id) {
if (entry.target.eql(other.target)) {
return entry.offset < other.offset;
}
const entry_name = ctx.getSymbolName(entry.target);
const other_name = ctx.getSymbolName(other.target);
return std.mem.lessThan(u8, entry_name, other_name);
}
return entry.segment_id < other.segment_id;
}
};
pub fn deinit(self: *Self, gpa: Allocator) void {
self.entries.deinit(gpa);
self.buffer.deinit(gpa);
}
pub fn size(self: Self) u64 {
return @intCast(u64, self.buffer.items.len);
}
pub fn finalize(self: *Self, gpa: Allocator, ctx: Ctx) !void {
if (self.entries.items.len == 0) return;
const writer = self.buffer.writer(gpa);
std.sort.sort(Entry, self.entries.items, ctx, Entry.lessThan);
var start: usize = 0;
var seg_id: ?u8 = null;
for (self.entries.items) |entry, i| {
if (seg_id != null and seg_id.? == entry.segment_id) continue;
try finalizeSegment(self.entries.items[start..i], ctx, writer);
seg_id = entry.segment_id;
start = i;
}
try finalizeSegment(self.entries.items[start..], ctx, writer);
try done(writer);
}
fn finalizeSegment(entries: []const Entry, ctx: Ctx, writer: anytype) !void {
if (entries.len == 0) return;
const seg_id = entries[0].segment_id;
try setSegmentOffset(seg_id, 0, writer);
var offset: u64 = 0;
var addend: i64 = 0;
var count: usize = 0;
var skip: u64 = 0;
var target: ?Target = null;
var state: enum {
start,
bind_single,
bind_times_skip,
} = .start;
var i: usize = 0;
while (i < entries.len) : (i += 1) {
const current = entries[i];
if (target == null or !target.?.eql(current.target)) {
switch (state) {
.start => {},
.bind_single => try doBind(writer),
.bind_times_skip => try doBindTimesSkip(count, skip, writer),
}
state = .start;
target = current.target;
const sym = ctx.getSymbol(current.target);
const name = ctx.getSymbolName(current.target);
const flags: u8 = if (sym.weakRef()) macho.BIND_SYMBOL_FLAGS_WEAK_IMPORT else 0;
const ordinal = @divTrunc(@bitCast(i16, sym.n_desc), macho.N_SYMBOL_RESOLVER);
try setSymbol(name, flags, writer);
try setTypePointer(writer);
try setDylibOrdinal(ordinal, writer);
if (current.addend != addend) {
addend = current.addend;
try setAddend(addend, writer);
}
}
log.debug("{x}, {d}, {x}, {?x}, {s}", .{ offset, count, skip, addend, @tagName(state) });
log.debug(" => {x}", .{current.offset});
switch (state) {
.start => {
if (current.offset < offset) {
try addAddr(@bitCast(u64, @intCast(i64, current.offset) - @intCast(i64, offset)), writer);
offset = offset - (offset - current.offset);
} else if (current.offset > offset) {
const delta = current.offset - offset;
try addAddr(delta, writer);
offset += delta;
}
state = .bind_single;
offset += @sizeOf(u64);
count = 1;
},
.bind_single => {
if (current.offset == offset) {
try doBind(writer);
state = .start;
} else if (current.offset > offset) {
const delta = current.offset - offset;
state = .bind_times_skip;
skip = @intCast(u64, delta);
offset += skip;
} else unreachable;
i -= 1;
},
.bind_times_skip => {
if (current.offset < offset) {
count -= 1;
if (count == 1) {
try doBindAddAddr(skip, writer);
} else {
try doBindTimesSkip(count, skip, writer);
}
state = .start;
offset = offset - (@sizeOf(u64) + skip);
i -= 2;
} else if (current.offset == offset) {
count += 1;
offset += @sizeOf(u64) + skip;
} else {
try doBindTimesSkip(count, skip, writer);
state = .start;
i -= 1;
}
},
}
}
switch (state) {
.start => unreachable,
.bind_single => try doBind(writer),
.bind_times_skip => try doBindTimesSkip(count, skip, writer),
}
}
pub fn write(self: Self, writer: anytype) !void {
if (self.size() == 0) return;
try writer.writeAll(self.buffer.items);
}
};
}
pub fn LazyBind(comptime Ctx: type, comptime Target: type) type {
return struct {
entries: std.ArrayListUnmanaged(Entry) = .{},
buffer: std.ArrayListUnmanaged(u8) = .{},
offsets: std.ArrayListUnmanaged(u32) = .{},
const Self = @This();
const Entry = struct {
target: Target,
offset: u64,
segment_id: u8,
addend: i64,
};
pub fn deinit(self: *Self, gpa: Allocator) void {
self.entries.deinit(gpa);
self.buffer.deinit(gpa);
self.offsets.deinit(gpa);
}
pub fn size(self: Self) u64 {
return @intCast(u64, self.buffer.items.len);
}
pub fn finalize(self: *Self, gpa: Allocator, ctx: Ctx) !void {
if (self.entries.items.len == 0) return;
try self.offsets.ensureTotalCapacityPrecise(gpa, self.entries.items.len);
var cwriter = std.io.countingWriter(self.buffer.writer(gpa));
const writer = cwriter.writer();
var addend: i64 = 0;
for (self.entries.items) |entry| {
self.offsets.appendAssumeCapacity(@intCast(u32, cwriter.bytes_written));
const sym = ctx.getSymbol(entry.target);
const name = ctx.getSymbolName(entry.target);
const flags: u8 = if (sym.weakRef()) macho.BIND_SYMBOL_FLAGS_WEAK_IMPORT else 0;
const ordinal = @divTrunc(@bitCast(i16, sym.n_desc), macho.N_SYMBOL_RESOLVER);
try setSegmentOffset(entry.segment_id, entry.offset, writer);
try setSymbol(name, flags, writer);
try setDylibOrdinal(ordinal, writer);
if (entry.addend != addend) {
try setAddend(entry.addend, writer);
addend = entry.addend;
}
try doBind(writer);
try done(writer);
}
}
pub fn write(self: Self, writer: anytype) !void {
if (self.size() == 0) return;
try writer.writeAll(self.buffer.items);
}
};
}
fn setSegmentOffset(segment_id: u8, offset: u64, writer: anytype) !void {
log.debug(">>> set segment: {d} and offset: {x}", .{ segment_id, offset });
try writer.writeByte(macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | @truncate(u4, segment_id));
try std.leb.writeULEB128(writer, offset);
}
fn setSymbol(name: []const u8, flags: u8, writer: anytype) !void {
log.debug(">>> set symbol: {s} with flags: {x}", .{ name, flags });
try writer.writeByte(macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | @truncate(u4, flags));
try writer.writeAll(name);
try writer.writeByte(0);
}
fn setTypePointer(writer: anytype) !void {
log.debug(">>> set type: {d}", .{macho.BIND_TYPE_POINTER});
try writer.writeByte(macho.BIND_OPCODE_SET_TYPE_IMM | @truncate(u4, macho.BIND_TYPE_POINTER));
}
fn setDylibOrdinal(ordinal: i16, writer: anytype) !void {
if (ordinal <= 0) {
switch (ordinal) {
macho.BIND_SPECIAL_DYLIB_SELF,
macho.BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE,
macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP,
=> {},
else => unreachable, // Invalid dylib special binding
}
log.debug(">>> set dylib special: {d}", .{ordinal});
const cast = @bitCast(u16, ordinal);
try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_SPECIAL_IMM | @truncate(u4, cast));
} else {
const cast = @bitCast(u16, ordinal);
log.debug(">>> set dylib ordinal: {d}", .{ordinal});
if (cast <= 0xf) {
try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | @truncate(u4, cast));
} else {
try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB);
try std.leb.writeULEB128(writer, cast);
}
}
}
fn setAddend(addend: i64, writer: anytype) !void {
log.debug(">>> set addend: {x}", .{addend});
try writer.writeByte(macho.BIND_OPCODE_SET_ADDEND_SLEB);
try std.leb.writeILEB128(writer, addend);
}
fn doBind(writer: anytype) !void {
log.debug(">>> bind", .{});
try writer.writeByte(macho.BIND_OPCODE_DO_BIND);
}
fn doBindAddAddr(addr: u64, writer: anytype) !void {
log.debug(">>> bind with add: {x}", .{addr});
if (std.mem.isAlignedGeneric(u64, addr, @sizeOf(u64))) {
const imm = @divExact(addr, @sizeOf(u64));
if (imm <= 0xf) {
try writer.writeByte(
macho.BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED | @truncate(u4, imm),
);
return;
}
}
try writer.writeByte(macho.BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB);
try std.leb.writeULEB128(writer, addr);
}
fn doBindTimesSkip(count: usize, skip: u64, writer: anytype) !void {
log.debug(">>> bind with count: {d} and skip: {x}", .{ count, skip });
try writer.writeByte(macho.BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB);
try std.leb.writeULEB128(writer, count);
try std.leb.writeULEB128(writer, skip);
}
fn addAddr(addr: u64, writer: anytype) !void {
log.debug(">>> add: {x}", .{addr});
try writer.writeByte(macho.BIND_OPCODE_ADD_ADDR_ULEB);
try std.leb.writeULEB128(writer, addr);
}
fn done(writer: anytype) !void {
log.debug(">>> done", .{});
try writer.writeByte(macho.BIND_OPCODE_DONE);
}
const TestContext = struct {
symbols: std.ArrayListUnmanaged(macho.nlist_64) = .{},
strtab: std.ArrayListUnmanaged(u8) = .{},
const Target = struct {
index: u32,
fn eql(this: Target, other: Target) bool {
return this.index == other.index;
}
};
fn deinit(ctx: *TestContext, gpa: Allocator) void {
ctx.symbols.deinit(gpa);
ctx.strtab.deinit(gpa);
}
fn addSymbol(ctx: *TestContext, gpa: Allocator, name: []const u8, ordinal: i16, flags: u16) !void {
const n_strx = try ctx.addString(gpa, name);
var n_desc = @bitCast(u16, ordinal * macho.N_SYMBOL_RESOLVER);
n_desc |= flags;
try ctx.symbols.append(gpa, .{
.n_value = 0,
.n_strx = n_strx,
.n_desc = n_desc,
.n_type = macho.N_EXT,
.n_sect = 0,
});
}
fn addString(ctx: *TestContext, gpa: Allocator, name: []const u8) !u32 {
const n_strx = @intCast(u32, ctx.strtab.items.len);
try ctx.strtab.appendSlice(gpa, name);
try ctx.strtab.append(gpa, 0);
return n_strx;
}
fn getSymbol(ctx: TestContext, target: Target) macho.nlist_64 {
return ctx.symbols.items[target.index];
}
fn getSymbolName(ctx: TestContext, target: Target) []const u8 {
const sym = ctx.getSymbol(target);
assert(sym.n_strx < ctx.strtab.items.len);
return std.mem.sliceTo(@ptrCast([*:0]const u8, ctx.strtab.items.ptr + sym.n_strx), 0);
}
};
fn generateTestContext() !TestContext {
const gpa = testing.allocator;
var ctx = TestContext{};
try ctx.addSymbol(gpa, "_import_1", 1, 0);
try ctx.addSymbol(gpa, "_import_2", 1, 0);
try ctx.addSymbol(gpa, "_import_3", 1, 0);
try ctx.addSymbol(gpa, "_import_4", 2, 0);
try ctx.addSymbol(gpa, "_import_5_weak", 2, macho.N_WEAK_REF);
try ctx.addSymbol(gpa, "_import_6", 2, 0);
return ctx;
}
test "bind - no entries" {
const gpa = testing.allocator;
var test_context = try generateTestContext();
defer test_context.deinit(gpa);
var bind = Bind(TestContext, TestContext.Target){};
defer bind.deinit(gpa);
try bind.finalize(gpa, test_context);
try testing.expectEqual(@as(u64, 0), bind.size());
}
test "bind - single entry" {
const gpa = testing.allocator;
var test_context = try generateTestContext();
defer test_context.deinit(gpa);
var bind = Bind(TestContext, TestContext.Target){};
defer bind.deinit(gpa);
try bind.entries.append(gpa, .{
.offset = 0x10,
.segment_id = 1,
.target = TestContext.Target{ .index = 0 },
.addend = 0,
});
try bind.finalize(gpa, test_context);
try testing.expectEqualSlices(u8, &[_]u8{
macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 1,
0x0,
macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | 0,
0x5f,
0x69,
0x6d,
0x70,
0x6f,
0x72,
0x74,
0x5f,
0x31,
0x0,
macho.BIND_OPCODE_SET_TYPE_IMM | 1,
macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | 1,
macho.BIND_OPCODE_ADD_ADDR_ULEB,
0x10,
macho.BIND_OPCODE_DO_BIND,
macho.BIND_OPCODE_DONE,
}, bind.buffer.items);
}
test "bind - multiple occurrences within the same segment" {
const gpa = testing.allocator;
var test_context = try generateTestContext();
defer test_context.deinit(gpa);
var bind = Bind(TestContext, TestContext.Target){};
defer bind.deinit(gpa);
try bind.entries.append(gpa, .{
.offset = 0x10,
.segment_id = 1,
.target = TestContext.Target{ .index = 0 },
.addend = 0,
});
try bind.entries.append(gpa, .{
.offset = 0x18,
.segment_id = 1,
.target = TestContext.Target{ .index = 0 },
.addend = 0,
});
try bind.entries.append(gpa, .{
.offset = 0x20,
.segment_id = 1,
.target = TestContext.Target{ .index = 0 },
.addend = 0,
});
try bind.entries.append(gpa, .{
.offset = 0x28,
.segment_id = 1,
.target = TestContext.Target{ .index = 0 },
.addend = 0,
});
try bind.finalize(gpa, test_context);
try testing.expectEqualSlices(u8, &[_]u8{
macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 1,
0x0,
macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | 0,
0x5f,
0x69,
0x6d,
0x70,
0x6f,
0x72,
0x74,
0x5f,
0x31,
0x0,
macho.BIND_OPCODE_SET_TYPE_IMM | 1,
macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | 1,
macho.BIND_OPCODE_ADD_ADDR_ULEB,
0x10,
macho.BIND_OPCODE_DO_BIND,
macho.BIND_OPCODE_DO_BIND,
macho.BIND_OPCODE_DO_BIND,
macho.BIND_OPCODE_DO_BIND,
macho.BIND_OPCODE_DONE,
}, bind.buffer.items);
}
test "bind - multiple occurrences with skip and addend" {
const gpa = testing.allocator;
var test_context = try generateTestContext();
defer test_context.deinit(gpa);
var bind = Bind(TestContext, TestContext.Target){};
defer bind.deinit(gpa);
try bind.entries.append(gpa, .{
.offset = 0x0,
.segment_id = 1,
.target = TestContext.Target{ .index = 0 },
.addend = 0x10,
});
try bind.entries.append(gpa, .{
.offset = 0x10,
.segment_id = 1,
.target = TestContext.Target{ .index = 0 },
.addend = 0x10,
});
try bind.entries.append(gpa, .{
.offset = 0x20,
.segment_id = 1,
.target = TestContext.Target{ .index = 0 },
.addend = 0x10,
});
try bind.entries.append(gpa, .{
.offset = 0x30,
.segment_id = 1,
.target = TestContext.Target{ .index = 0 },
.addend = 0x10,
});
try bind.finalize(gpa, test_context);
try testing.expectEqualSlices(u8, &[_]u8{
macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 1,
0x0,
macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | 0,
0x5f,
0x69,
0x6d,
0x70,
0x6f,
0x72,
0x74,
0x5f,
0x31,
0x0,
macho.BIND_OPCODE_SET_TYPE_IMM | 1,
macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | 1,
macho.BIND_OPCODE_SET_ADDEND_SLEB,
0x10,
macho.BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB,
0x4,
0x8,
macho.BIND_OPCODE_DONE,
}, bind.buffer.items);
}
test "bind - complex" {
const gpa = testing.allocator;
var test_context = try generateTestContext();
defer test_context.deinit(gpa);
var bind = Bind(TestContext, TestContext.Target){};
defer bind.deinit(gpa);
try bind.entries.append(gpa, .{
.offset = 0x58,
.segment_id = 1,
.target = TestContext.Target{ .index = 0 },
.addend = 0,
});
try bind.entries.append(gpa, .{
.offset = 0x100,
.segment_id = 1,
.target = TestContext.Target{ .index = 1 },
.addend = 0x10,
});
try bind.entries.append(gpa, .{
.offset = 0x110,
.segment_id = 1,
.target = TestContext.Target{ .index = 1 },
.addend = 0x10,
});
try bind.entries.append(gpa, .{
.offset = 0x130,
.segment_id = 1,
.target = TestContext.Target{ .index = 1 },
.addend = 0x10,
});
try bind.entries.append(gpa, .{
.offset = 0x140,
.segment_id = 1,
.target = TestContext.Target{ .index = 1 },
.addend = 0x10,
});
try bind.entries.append(gpa, .{
.offset = 0x148,
.segment_id = 1,
.target = TestContext.Target{ .index = 2 },
.addend = 0,
});
try bind.finalize(gpa, test_context);
try testing.expectEqualSlices(u8, &[_]u8{
macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 1,
0x0,
macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | 0,
0x5f,
0x69,
0x6d,
0x70,
0x6f,
0x72,
0x74,
0x5f,
0x31,
0x0,
macho.BIND_OPCODE_SET_TYPE_IMM | 1,
macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | 1,
macho.BIND_OPCODE_ADD_ADDR_ULEB,
0x58,
macho.BIND_OPCODE_DO_BIND,
macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | 0,
0x5f,
0x69,
0x6d,
0x70,
0x6f,
0x72,
0x74,
0x5f,
0x32,
0x0,
macho.BIND_OPCODE_SET_TYPE_IMM | 1,
macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | 1,
macho.BIND_OPCODE_SET_ADDEND_SLEB,
0x10,
macho.BIND_OPCODE_ADD_ADDR_ULEB,
0xa0,
0x1,
macho.BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB,
0x2,
0x8,
macho.BIND_OPCODE_ADD_ADDR_ULEB,
0x10,
macho.BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB,
0x2,
0x8,
macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | 0,
0x5f,
0x69,
0x6d,
0x70,
0x6f,
0x72,
0x74,
0x5f,
0x33,
0x0,
macho.BIND_OPCODE_SET_TYPE_IMM | 1,
macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | 1,
macho.BIND_OPCODE_SET_ADDEND_SLEB,
0x0,
macho.BIND_OPCODE_ADD_ADDR_ULEB,
0xf8,
0xff,
0xff,
0xff,
0xff,
0xff,
0xff,
0xff,
0xff,
0x1,
macho.BIND_OPCODE_DO_BIND,
macho.BIND_OPCODE_DONE,
}, bind.buffer.items);
}
test "lazy bind" {
const gpa = testing.allocator;
var test_context = try generateTestContext();
defer test_context.deinit(gpa);
var bind = LazyBind(TestContext, TestContext.Target){};
defer bind.deinit(gpa);
try bind.entries.append(gpa, .{
.offset = 0x10,
.segment_id = 1,
.target = TestContext.Target{ .index = 0 },
.addend = 0,
});
try bind.entries.append(gpa, .{
.offset = 0x20,
.segment_id = 2,
.target = TestContext.Target{ .index = 1 },
.addend = 0x10,
});
try bind.finalize(gpa, test_context);
try testing.expectEqualSlices(u8, &[_]u8{
macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 1,
0x10,
macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | 0,
0x5f,
0x69,
0x6d,
0x70,
0x6f,
0x72,
0x74,
0x5f,
0x31,
0x0,
macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | 1,
macho.BIND_OPCODE_DO_BIND,
macho.BIND_OPCODE_DONE,
macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 2,
0x20,
macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | 0,
0x5f,
0x69,
0x6d,
0x70,
0x6f,
0x72,
0x74,
0x5f,
0x32,
0x0,
macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | 1,
macho.BIND_OPCODE_SET_ADDEND_SLEB,
0x10,
macho.BIND_OPCODE_DO_BIND,
macho.BIND_OPCODE_DONE,
}, bind.buffer.items);
}

View File

@ -9,7 +9,6 @@ const math = std.math;
const mem = std.mem;
const aarch64 = @import("../../arch/aarch64/bits.zig");
const bind = @import("bind.zig");
const dead_strip = @import("dead_strip.zig");
const fat = @import("fat.zig");
const link = @import("../../link.zig");
@ -32,6 +31,10 @@ const Object = @import("Object.zig");
const StringTable = @import("../strtab.zig").StringTable;
const Trie = @import("Trie.zig");
const Bind = @import("dyld_info/bind.zig").Bind(*const Zld, SymbolWithLoc);
const LazyBind = @import("dyld_info/bind.zig").LazyBind(*const Zld, SymbolWithLoc);
const Rebase = @import("dyld_info/Rebase.zig");
pub const Zld = struct {
gpa: Allocator,
file: fs.File,
@ -1778,14 +1781,14 @@ pub const Zld = struct {
fn collectRebaseDataFromContainer(
self: *Zld,
sect_id: u8,
pointers: *std.ArrayList(bind.Pointer),
rebase: *Rebase,
container: anytype,
) !void {
const slice = self.sections.slice();
const segment_index = slice.items(.segment_index)[sect_id];
const seg = self.getSegment(sect_id);
try pointers.ensureUnusedCapacity(container.items.len);
try rebase.entries.ensureUnusedCapacity(self.gpa, container.items.len);
for (container.items) |entry| {
const target_sym = entry.getTargetSymbol(self);
@ -1796,19 +1799,19 @@ pub const Zld = struct {
log.debug(" | rebase at {x}", .{base_offset});
pointers.appendAssumeCapacity(.{
rebase.entries.appendAssumeCapacity(.{
.offset = base_offset,
.segment_id = segment_index,
});
}
}
fn collectRebaseData(self: *Zld, pointers: *std.ArrayList(bind.Pointer)) !void {
fn collectRebaseData(self: *Zld, rebase: *Rebase, reverse_lookups: [][]u32) !void {
log.debug("collecting rebase data", .{});
// First, unpack GOT entries
if (self.getSectionByName("__DATA_CONST", "__got")) |sect_id| {
try self.collectRebaseDataFromContainer(sect_id, pointers, self.got_entries);
try self.collectRebaseDataFromContainer(sect_id, rebase, self.got_entries);
}
const slice = self.sections.slice();
@ -1820,7 +1823,7 @@ pub const Zld = struct {
const seg = self.getSegment(sect_id);
var atom_index = slice.items(.first_atom_index)[sect_id];
try pointers.ensureUnusedCapacity(self.stubs.items.len);
try rebase.entries.ensureUnusedCapacity(self.gpa, self.stubs.items.len);
while (true) {
const atom = self.getAtom(atom_index);
@ -1829,7 +1832,7 @@ pub const Zld = struct {
log.debug(" | rebase at {x}", .{base_offset});
pointers.appendAssumeCapacity(.{
rebase.entries.appendAssumeCapacity(.{
.offset = base_offset,
.segment_id = segment_index,
});
@ -1896,13 +1899,16 @@ pub const Zld = struct {
},
else => unreachable,
}
const target = Atom.parseRelocTarget(self, atom_index, rel, reverse_lookups[atom.getFile().?]);
const target_sym = self.getSymbol(target);
if (target_sym.undf()) continue;
const base_offset = @intCast(i32, sym.n_value - segment.vmaddr);
const rel_offset = rel.r_address - base_rel_offset;
const offset = @intCast(u64, base_offset + rel_offset);
log.debug(" | rebase at {x}", .{offset});
try pointers.append(.{
try rebase.entries.append(self.gpa, .{
.offset = offset,
.segment_id = segment_index,
});
@ -1914,19 +1920,21 @@ pub const Zld = struct {
} else break;
}
}
try rebase.finalize(self.gpa);
}
fn collectBindDataFromContainer(
self: *Zld,
sect_id: u8,
pointers: *std.ArrayList(bind.Pointer),
bind: *Bind,
container: anytype,
) !void {
const slice = self.sections.slice();
const segment_index = slice.items(.segment_index)[sect_id];
const seg = self.getSegment(sect_id);
try pointers.ensureUnusedCapacity(container.items.len);
try bind.entries.ensureUnusedCapacity(self.gpa, container.items.len);
for (container.items) |entry| {
const bind_sym_name = entry.getTargetSymbolName(self);
@ -1937,7 +1945,6 @@ pub const Zld = struct {
const base_offset = sym.n_value - seg.vmaddr;
const dylib_ordinal = @divTrunc(@bitCast(i16, bind_sym.n_desc), macho.N_SYMBOL_RESOLVER);
var flags: u4 = 0;
log.debug(" | bind at {x}, import('{s}') in dylib({d})", .{
base_offset,
bind_sym_name,
@ -1945,29 +1952,27 @@ pub const Zld = struct {
});
if (bind_sym.weakRef()) {
log.debug(" | marking as weak ref ", .{});
flags |= @truncate(u4, macho.BIND_SYMBOL_FLAGS_WEAK_IMPORT);
}
pointers.appendAssumeCapacity(.{
bind.entries.appendAssumeCapacity(.{
.target = entry.target,
.offset = base_offset,
.segment_id = segment_index,
.dylib_ordinal = dylib_ordinal,
.name = bind_sym_name,
.bind_flags = flags,
.addend = 0,
});
}
}
fn collectBindData(self: *Zld, pointers: *std.ArrayList(bind.Pointer), reverse_lookups: [][]u32) !void {
fn collectBindData(self: *Zld, bind: *Bind, reverse_lookups: [][]u32) !void {
log.debug("collecting bind data", .{});
// First, unpack GOT section
if (self.getSectionByName("__DATA_CONST", "__got")) |sect_id| {
try self.collectBindDataFromContainer(sect_id, pointers, self.got_entries);
try self.collectBindDataFromContainer(sect_id, bind, self.got_entries);
}
// Next, unpack TLV pointers section
if (self.getSectionByName("__DATA", "__thread_ptrs")) |sect_id| {
try self.collectBindDataFromContainer(sect_id, pointers, self.tlv_ptr_entries);
try self.collectBindDataFromContainer(sect_id, bind, self.tlv_ptr_entries);
}
// Finally, unpack the rest.
@ -2033,27 +2038,27 @@ pub const Zld = struct {
const bind_sym = self.getSymbol(global);
if (!bind_sym.undf()) continue;
const base_offset = @intCast(i32, sym.n_value - segment.vmaddr);
const rel_offset = rel.r_address - base_rel_offset;
const base_offset = sym.n_value - segment.vmaddr;
const rel_offset = @intCast(u32, rel.r_address - base_rel_offset);
const offset = @intCast(u64, base_offset + rel_offset);
const code = Atom.getAtomCode(self, atom_index);
const addend = mem.readIntLittle(i64, code[rel_offset..][0..8]);
const dylib_ordinal = @divTrunc(@bitCast(i16, bind_sym.n_desc), macho.N_SYMBOL_RESOLVER);
var flags: u4 = 0;
log.debug(" | bind at {x}, import('{s}') in dylib({d})", .{
base_offset,
bind_sym_name,
dylib_ordinal,
});
log.debug(" | with addend {x}", .{addend});
if (bind_sym.weakRef()) {
log.debug(" | marking as weak ref ", .{});
flags |= @truncate(u4, macho.BIND_SYMBOL_FLAGS_WEAK_IMPORT);
}
try pointers.append(.{
try bind.entries.append(self.gpa, .{
.target = global,
.offset = offset,
.segment_id = segment_index,
.dylib_ordinal = dylib_ordinal,
.name = bind_sym_name,
.bind_flags = flags,
.addend = addend,
});
}
}
@ -2062,9 +2067,11 @@ pub const Zld = struct {
} else break;
}
}
try bind.finalize(self.gpa, self);
}
fn collectLazyBindData(self: *Zld, pointers: *std.ArrayList(bind.Pointer)) !void {
fn collectLazyBindData(self: *Zld, lazy_bind: *LazyBind) !void {
const sect_id = self.getSectionByName("__DATA", "__la_symbol_ptr") orelse return;
log.debug("collecting lazy bind data", .{});
@ -2075,7 +2082,7 @@ pub const Zld = struct {
var atom_index = slice.items(.first_atom_index)[sect_id];
// TODO: we actually don't need to store lazy pointer atoms as they are synthetically generated by the linker
try pointers.ensureUnusedCapacity(self.stubs.items.len);
try lazy_bind.entries.ensureUnusedCapacity(self.gpa, self.stubs.items.len);
var count: u32 = 0;
while (true) : (count += 1) {
@ -2090,7 +2097,6 @@ pub const Zld = struct {
const bind_sym = stub_entry.getTargetSymbol(self);
const bind_sym_name = stub_entry.getTargetSymbolName(self);
const dylib_ordinal = @divTrunc(@bitCast(i16, bind_sym.n_desc), macho.N_SYMBOL_RESOLVER);
var flags: u4 = 0;
log.debug(" | lazy bind at {x}, import('{s}') in dylib({d})", .{
base_offset,
bind_sym_name,
@ -2098,20 +2104,20 @@ pub const Zld = struct {
});
if (bind_sym.weakRef()) {
log.debug(" | marking as weak ref ", .{});
flags |= @truncate(u4, macho.BIND_SYMBOL_FLAGS_WEAK_IMPORT);
}
pointers.appendAssumeCapacity(.{
lazy_bind.entries.appendAssumeCapacity(.{
.target = stub_entry.target,
.offset = base_offset,
.segment_id = segment_index,
.dylib_ordinal = dylib_ordinal,
.name = bind_sym_name,
.bind_flags = flags,
.addend = 0,
});
if (atom.next_index) |next_index| {
atom_index = next_index;
} else break;
}
try lazy_bind.finalize(self.gpa, self);
}
fn collectExportData(self: *Zld, trie: *Trie) !void {
@ -2161,17 +2167,17 @@ pub const Zld = struct {
fn writeDyldInfoData(self: *Zld, reverse_lookups: [][]u32) !void {
const gpa = self.gpa;
var rebase_pointers = std.ArrayList(bind.Pointer).init(gpa);
defer rebase_pointers.deinit();
try self.collectRebaseData(&rebase_pointers);
var rebase = Rebase{};
defer rebase.deinit(gpa);
try self.collectRebaseData(&rebase, reverse_lookups);
var bind_pointers = std.ArrayList(bind.Pointer).init(gpa);
defer bind_pointers.deinit();
try self.collectBindData(&bind_pointers, reverse_lookups);
var bind = Bind{};
defer bind.deinit(gpa);
try self.collectBindData(&bind, reverse_lookups);
var lazy_bind_pointers = std.ArrayList(bind.Pointer).init(gpa);
defer lazy_bind_pointers.deinit();
try self.collectLazyBindData(&lazy_bind_pointers);
var lazy_bind = LazyBind{};
defer lazy_bind.deinit(gpa);
try self.collectLazyBindData(&lazy_bind);
var trie = Trie{};
defer trie.deinit(gpa);
@ -2180,17 +2186,17 @@ pub const Zld = struct {
const link_seg = self.getLinkeditSegmentPtr();
assert(mem.isAlignedGeneric(u64, link_seg.fileoff, @alignOf(u64)));
const rebase_off = link_seg.fileoff;
const rebase_size = try bind.rebaseInfoSize(rebase_pointers.items);
const rebase_size = rebase.size();
const rebase_size_aligned = mem.alignForwardGeneric(u64, rebase_size, @alignOf(u64));
log.debug("writing rebase info from 0x{x} to 0x{x}", .{ rebase_off, rebase_off + rebase_size_aligned });
const bind_off = rebase_off + rebase_size_aligned;
const bind_size = try bind.bindInfoSize(bind_pointers.items);
const bind_size = bind.size();
const bind_size_aligned = mem.alignForwardGeneric(u64, bind_size, @alignOf(u64));
log.debug("writing bind info from 0x{x} to 0x{x}", .{ bind_off, bind_off + bind_size_aligned });
const lazy_bind_off = bind_off + bind_size_aligned;
const lazy_bind_size = try bind.lazyBindInfoSize(lazy_bind_pointers.items);
const lazy_bind_size = lazy_bind.size();
const lazy_bind_size_aligned = mem.alignForwardGeneric(u64, lazy_bind_size, @alignOf(u64));
log.debug("writing lazy bind info from 0x{x} to 0x{x}", .{
lazy_bind_off,
@ -2214,13 +2220,13 @@ pub const Zld = struct {
var stream = std.io.fixedBufferStream(buffer);
const writer = stream.writer();
try bind.writeRebaseInfo(rebase_pointers.items, writer);
try rebase.write(writer);
try stream.seekTo(bind_off - rebase_off);
try bind.writeBindInfo(bind_pointers.items, writer);
try bind.write(writer);
try stream.seekTo(lazy_bind_off - rebase_off);
try bind.writeLazyBindInfo(lazy_bind_pointers.items, writer);
try lazy_bind.write(writer);
try stream.seekTo(export_off - rebase_off);
_ = try trie.write(writer);
@ -2231,10 +2237,7 @@ pub const Zld = struct {
});
try self.file.pwriteAll(buffer, rebase_off);
const offset = math.cast(usize, lazy_bind_off - rebase_off) orelse return error.Overflow;
const size = math.cast(usize, lazy_bind_size) orelse return error.Overflow;
try self.populateLazyBindOffsetsInStubHelper(buffer[offset..][0..size]);
try self.populateLazyBindOffsetsInStubHelper(lazy_bind);
self.dyld_info_cmd.rebase_off = @intCast(u32, rebase_off);
self.dyld_info_cmd.rebase_size = @intCast(u32, rebase_size_aligned);
@ -2246,116 +2249,37 @@ pub const Zld = struct {
self.dyld_info_cmd.export_size = @intCast(u32, export_size_aligned);
}
fn populateLazyBindOffsetsInStubHelper(self: *Zld, buffer: []const u8) !void {
const gpa = self.gpa;
fn populateLazyBindOffsetsInStubHelper(self: *Zld, lazy_bind: LazyBind) !void {
if (lazy_bind.size() == 0) return;
const stub_helper_section_index = self.getSectionByName("__TEXT", "__stub_helper") orelse return;
const la_symbol_ptr_section_index = self.getSectionByName("__DATA", "__la_symbol_ptr") orelse return;
if (self.stub_helper_preamble_sym_index == null) return;
const stub_helper_section_index = self.getSectionByName("__TEXT", "__stub_helper").?;
assert(self.stub_helper_preamble_sym_index != null);
const section = self.sections.get(stub_helper_section_index);
const last_atom_index = section.last_atom_index;
var table = std.AutoHashMap(i64, AtomIndex).init(gpa);
defer table.deinit();
{
var stub_atom_index = last_atom_index;
var laptr_atom_index = self.sections.items(.last_atom_index)[la_symbol_ptr_section_index];
const base_addr = blk: {
const segment_index = self.getSegmentByName("__DATA").?;
const seg = self.segments.items[segment_index];
break :blk seg.vmaddr;
};
while (true) {
const stub_atom = self.getAtom(stub_atom_index);
const laptr_atom = self.getAtom(laptr_atom_index);
const laptr_off = blk: {
const sym = self.getSymbolPtr(laptr_atom.getSymbolWithLoc());
break :blk @intCast(i64, sym.n_value - base_addr);
};
try table.putNoClobber(laptr_off, stub_atom_index);
if (laptr_atom.prev_index) |prev_index| {
laptr_atom_index = prev_index;
stub_atom_index = stub_atom.prev_index.?;
} else break;
}
}
var stream = std.io.fixedBufferStream(buffer);
var reader = stream.reader();
var offsets = std.ArrayList(struct { sym_offset: i64, offset: u32 }).init(gpa);
try offsets.append(.{ .sym_offset = undefined, .offset = 0 });
defer offsets.deinit();
var valid_block = false;
while (true) {
const inst = reader.readByte() catch |err| switch (err) {
error.EndOfStream => break,
};
const opcode: u8 = inst & macho.BIND_OPCODE_MASK;
switch (opcode) {
macho.BIND_OPCODE_DO_BIND => {
valid_block = true;
},
macho.BIND_OPCODE_DONE => {
if (valid_block) {
const offset = try stream.getPos();
try offsets.append(.{ .sym_offset = undefined, .offset = @intCast(u32, offset) });
}
valid_block = false;
},
macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM => {
var next = try reader.readByte();
while (next != @as(u8, 0)) {
next = try reader.readByte();
}
},
macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB => {
var inserted = offsets.pop();
inserted.sym_offset = try std.leb.readILEB128(i64, reader);
try offsets.append(inserted);
},
macho.BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB => {
_ = try std.leb.readULEB128(u64, reader);
},
macho.BIND_OPCODE_SET_ADDEND_SLEB => {
_ = try std.leb.readILEB128(i64, reader);
},
else => {},
}
}
const header = self.sections.items(.header)[stub_helper_section_index];
const stub_offset: u4 = switch (self.options.target.cpu.arch) {
.x86_64 => 1,
.aarch64 => 2 * @sizeOf(u32),
else => unreachable,
};
var buf: [@sizeOf(u32)]u8 = undefined;
_ = offsets.pop();
const header = section.header;
var atom_index = section.first_atom_index;
atom_index = self.getAtom(atom_index).next_index.?; // skip preamble
while (offsets.popOrNull()) |bind_offset| {
const atom_index = table.get(bind_offset.sym_offset).?;
var index: usize = 0;
while (true) {
const atom = self.getAtom(atom_index);
const sym = self.getSymbol(atom.getSymbolWithLoc());
const atom_sym = self.getSymbol(atom.getSymbolWithLoc());
const file_offset = header.offset + atom_sym.n_value - header.addr + stub_offset;
const bind_offset = lazy_bind.offsets.items[index];
const file_offset = header.offset + sym.n_value - header.addr + stub_offset;
mem.writeIntLittle(u32, &buf, bind_offset.offset);
log.debug("writing lazy bind offset 0x{x} in stub helper at 0x{x}", .{ bind_offset, file_offset });
log.debug("writing lazy bind offset in stub helper of 0x{x} for symbol {s} at offset 0x{x}", .{
bind_offset.offset,
self.getSymbolName(atom.getSymbolWithLoc()),
file_offset,
});
try self.file.pwriteAll(mem.asBytes(&bind_offset), file_offset);
try self.file.pwriteAll(&buf, file_offset);
if (atom.next_index) |next_index| {
atom_index = next_index;
index += 1;
} else break;
}
}
@ -3018,12 +2942,17 @@ pub const Zld = struct {
}
/// Returns symbol described by `sym_with_loc` descriptor.
pub fn getSymbol(self: *Zld, sym_with_loc: SymbolWithLoc) macho.nlist_64 {
return self.getSymbolPtr(sym_with_loc).*;
pub fn getSymbol(self: *const Zld, sym_with_loc: SymbolWithLoc) macho.nlist_64 {
if (sym_with_loc.getFile()) |file| {
const object = &self.objects.items[file];
return object.symtab[sym_with_loc.sym_index];
} else {
return self.locals.items[sym_with_loc.sym_index];
}
}
/// Returns name of the symbol described by `sym_with_loc` descriptor.
pub fn getSymbolName(self: *Zld, sym_with_loc: SymbolWithLoc) []const u8 {
pub fn getSymbolName(self: *const Zld, sym_with_loc: SymbolWithLoc) []const u8 {
if (sym_with_loc.getFile()) |file| {
const object = self.objects.items[file];
return object.getSymbolName(sym_with_loc.sym_index);

View File

@ -12,18 +12,18 @@ pub fn build(b: *Builder) void {
.os_tag = .macos,
};
testUuid(b, test_step, .ReleaseSafe, aarch64_macos, "46b333df88f5314686fc0cba3b939ca8");
testUuid(b, test_step, .ReleaseFast, aarch64_macos, "46b333df88f5314686fc0cba3b939ca8");
testUuid(b, test_step, .ReleaseSmall, aarch64_macos, "46b333df88f5314686fc0cba3b939ca8");
testUuid(b, test_step, .ReleaseSafe, aarch64_macos, "af0f4c21a07c30daba59213d80262e45");
testUuid(b, test_step, .ReleaseFast, aarch64_macos, "af0f4c21a07c30daba59213d80262e45");
testUuid(b, test_step, .ReleaseSmall, aarch64_macos, "af0f4c21a07c30daba59213d80262e45");
const x86_64_macos = std.zig.CrossTarget{
.cpu_arch = .x86_64,
.os_tag = .macos,
};
testUuid(b, test_step, .ReleaseSafe, x86_64_macos, "342ac765194131e1bad5692b9e0e54a4");
testUuid(b, test_step, .ReleaseFast, x86_64_macos, "342ac765194131e1bad5692b9e0e54a4");
testUuid(b, test_step, .ReleaseSmall, x86_64_macos, "f119310e24773ecf8ec42e09d0379dad");
testUuid(b, test_step, .ReleaseSafe, x86_64_macos, "63f47191c7153f5fba48bd63cb2f5f57");
testUuid(b, test_step, .ReleaseFast, x86_64_macos, "63f47191c7153f5fba48bd63cb2f5f57");
testUuid(b, test_step, .ReleaseSmall, x86_64_macos, "e7bba66220e33eda9e73ab293ccf93d2");
}
fn testUuid(