macho: split section into subsections if requested and/or possible

This commit is contained in:
Jakub Konka 2022-07-04 20:40:10 +02:00
parent d042b88c11
commit 03feea0fb2
4 changed files with 513 additions and 231 deletions

View File

@ -57,6 +57,8 @@ const SystemLib = struct {
weak: bool = false,
};
const N_DESC_GCED: u16 = @bitCast(u16, @as(i16, -1));
base: File,
/// If this is not null, an object file is created by LLVM and linked with LLD afterwards.
@ -256,6 +258,8 @@ unnamed_const_atoms: UnnamedConstTable = .{},
/// TODO consolidate this.
decls: std.AutoArrayHashMapUnmanaged(Module.Decl.Index, ?MatchingSection) = .{},
gc_roots: std.AutoHashMapUnmanaged(*Atom, void) = .{},
const Entry = struct {
target: Atom.Relocation.Target,
atom: *Atom,
@ -1165,6 +1169,8 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
const use_llvm = build_options.have_llvm and self.base.options.use_llvm;
if (use_llvm or use_stage1) {
self.logAtoms();
try self.gcAtoms();
try self.pruneAndSortSections();
try self.allocateSegments();
try self.allocateLocals();
@ -1173,9 +1179,10 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
try self.allocateSpecialSymbols();
try self.allocateGlobals();
if (build_options.enable_logging) {
if (build_options.enable_logging or true) {
self.logSymtab();
self.logSectionOrdinals();
self.logAtoms();
}
if (use_llvm or use_stage1) {
@ -2177,6 +2184,7 @@ pub fn createEmptyAtom(self: *MachO, local_sym_index: u32, size: u64, alignment:
try atom.code.resize(self.base.allocator, size_usize);
mem.set(u8, atom.code.items, 0);
try self.atom_by_index_table.putNoClobber(self.base.allocator, local_sym_index, atom);
try self.managed_atoms.append(self.base.allocator, atom);
return atom;
}
@ -3298,12 +3306,7 @@ fn resolveDyldStubBinder(self: *MachO) !void {
const vaddr = try self.allocateAtom(atom, @sizeOf(u64), 8, match);
log.debug("allocated {s} atom at 0x{x}", .{ self.getString(sym.n_strx), vaddr });
atom_sym.n_value = vaddr;
} else {
const seg = &self.load_commands.items[self.data_const_segment_cmd_index.?].segment;
const sect = &seg.sections.items[self.got_section_index.?];
sect.size += atom.size;
try self.addAtomToSection(atom, match);
}
} else try self.addAtomToSection(atom, match);
atom_sym.n_sect = @intCast(u8, self.section_ordinals.getIndex(match).? + 1);
}
@ -3564,6 +3567,7 @@ pub fn deinit(self: *MachO) void {
self.symbol_resolver.deinit(self.base.allocator);
self.unresolved.deinit(self.base.allocator);
self.tentatives.deinit(self.base.allocator);
self.gc_roots.deinit(self.base.allocator);
for (self.objects.items) |*object| {
object.deinit(self.base.allocator);
@ -3916,7 +3920,6 @@ pub fn lowerUnnamedConst(self: *MachO, typed_value: TypedValue, decl_index: Modu
const required_alignment = typed_value.ty.abiAlignment(self.base.options.target);
const local_sym_index = try self.allocateLocalSymbol();
const atom = try self.createEmptyAtom(local_sym_index, @sizeOf(u64), math.log2(required_alignment));
try self.atom_by_index_table.putNoClobber(self.base.allocator, local_sym_index, atom);
const res = try codegen.generateSymbol(&self.base, decl.srcLoc(), typed_value, &code_buffer, .none, .{
.parent_atom_index = local_sym_index,
@ -5597,7 +5600,7 @@ fn pruneAndSortSectionsInSegment(self: *MachO, maybe_seg_id: *?u16, indices: []*
const old_idx = maybe_index.* orelse continue;
const sect = sections[old_idx];
if (sect.size == 0) {
log.debug("pruning section {s},{s}", .{ sect.segName(), sect.sectName() });
log.warn("pruning section {s},{s}", .{ sect.segName(), sect.sectName() });
maybe_index.* = null;
seg.inner.cmdsize -= @sizeOf(macho.section_64);
seg.inner.nsects -= 1;
@ -5630,7 +5633,7 @@ fn pruneAndSortSectionsInSegment(self: *MachO, maybe_seg_id: *?u16, indices: []*
if (seg.inner.nsects == 0 and !mem.eql(u8, "__TEXT", seg.inner.segName())) {
// Segment has now become empty, so mark it as such
log.debug("marking segment {s} as dead", .{seg.inner.segName()});
log.warn("marking segment {s} as dead", .{seg.inner.segName()});
seg.inner.cmd = @intToEnum(macho.LC, 0);
maybe_seg_id.* = null;
}
@ -5712,6 +5715,189 @@ fn pruneAndSortSections(self: *MachO) !void {
self.sections_order_dirty = false;
}
fn gcAtoms(self: *MachO) !void {
const dead_strip = self.base.options.gc_sections orelse false;
if (!dead_strip) return;
// Add all exports as GC roots
for (self.globals.items) |sym| {
if (sym.n_type == 0) continue;
const resolv = self.symbol_resolver.get(sym.n_strx).?;
assert(resolv.where == .global);
const gc_root = self.atom_by_index_table.get(resolv.local_sym_index) orelse {
log.warn("skipping {s}", .{self.getString(sym.n_strx)});
continue;
};
_ = try self.gc_roots.getOrPut(self.base.allocator, gc_root);
}
// if (self.tlv_ptrs_section_index) |sect| {
// var atom = self.atoms.get(.{
// .seg = self.data_segment_cmd_index.?,
// .sect = sect,
// }).?;
// while (true) {
// _ = try self.gc_roots.getOrPut(self.base.allocator, atom);
// if (atom.prev) |prev| {
// atom = prev;
// } else break;
// }
// }
// Add any atom targeting an import as GC root
var atoms_it = self.atoms.iterator();
while (atoms_it.next()) |entry| {
var atom = entry.value_ptr.*;
while (true) {
for (atom.relocs.items) |rel| {
if ((try Atom.getTargetAtom(rel, self)) == null) switch (rel.target) {
.local => {},
.global => |n_strx| {
const resolv = self.symbol_resolver.get(n_strx).?;
switch (resolv.where) {
.global => {},
.undef => {
_ = try self.gc_roots.getOrPut(self.base.allocator, atom);
break;
},
}
},
};
}
if (atom.prev) |prev| {
atom = prev;
} else break;
}
}
var stack = std.ArrayList(*Atom).init(self.base.allocator);
defer stack.deinit();
try stack.ensureUnusedCapacity(self.gc_roots.count());
var retained = std.AutoHashMap(*Atom, void).init(self.base.allocator);
defer retained.deinit();
try retained.ensureUnusedCapacity(self.gc_roots.count());
log.warn("GC roots:", .{});
var gc_roots_it = self.gc_roots.keyIterator();
while (gc_roots_it.next()) |gc_root| {
self.logAtom(gc_root.*);
stack.appendAssumeCapacity(gc_root.*);
retained.putAssumeCapacityNoClobber(gc_root.*, {});
}
log.warn("walking tree...", .{});
while (stack.popOrNull()) |source_atom| {
for (source_atom.relocs.items) |rel| {
if (try Atom.getTargetAtom(rel, self)) |target_atom| {
const gop = try retained.getOrPut(target_atom);
if (!gop.found_existing) {
log.warn(" RETAINED ATOM(%{d}) -> ATOM(%{d})", .{
source_atom.local_sym_index,
target_atom.local_sym_index,
});
try stack.append(target_atom);
}
}
}
}
atoms_it = self.atoms.iterator();
while (atoms_it.next()) |entry| {
const match = entry.key_ptr.*;
if (self.text_segment_cmd_index) |seg| {
if (seg == match.seg) {
if (self.eh_frame_section_index) |sect| {
if (sect == match.sect) continue;
}
}
}
if (self.data_segment_cmd_index) |seg| {
if (seg == match.seg) {
if (self.rustc_section_index) |sect| {
if (sect == match.sect) continue;
}
}
}
const seg = &self.load_commands.items[match.seg].segment;
const sect = &seg.sections.items[match.sect];
var atom = entry.value_ptr.*;
log.warn("GCing atoms in {s},{s}", .{ sect.segName(), sect.sectName() });
while (true) {
const orig_prev = atom.prev;
if (!retained.contains(atom)) {
// Dead atom; remove.
log.warn(" DEAD ATOM(%{d})", .{atom.local_sym_index});
const sym = &self.locals.items[atom.local_sym_index];
sym.n_desc = N_DESC_GCED;
if (self.symbol_resolver.getPtr(sym.n_strx)) |resolv| {
if (resolv.local_sym_index == atom.local_sym_index) {
const global = &self.globals.items[resolv.where_index];
global.n_desc = N_DESC_GCED;
}
}
for (self.got_entries.items) |got_entry| {
if (got_entry.atom == atom) {
_ = self.got_entries_table.swapRemove(got_entry.target);
break;
}
}
for (self.stubs.items) |stub, i| {
if (stub == atom) {
_ = self.stubs_table.swapRemove(@intCast(u32, i));
break;
}
}
for (atom.contained.items) |sym_off| {
const inner = &self.locals.items[sym_off.local_sym_index];
inner.n_desc = N_DESC_GCED;
if (self.symbol_resolver.getPtr(inner.n_strx)) |resolv| {
if (resolv.local_sym_index == atom.local_sym_index) {
const global = &self.globals.items[resolv.where_index];
global.n_desc = N_DESC_GCED;
}
}
}
log.warn(" BEFORE size = {x}", .{sect.size});
sect.size -= atom.size;
log.warn(" AFTER size = {x}", .{sect.size});
if (atom.prev) |prev| {
prev.next = atom.next;
}
if (atom.next) |next| {
next.prev = atom.prev;
} else {
// TODO I think a null would be better here.
// The section will be GCed in the next step.
entry.value_ptr.* = if (atom.prev) |prev| prev else undefined;
}
}
if (orig_prev) |prev| {
atom = prev;
} else break;
}
}
}
fn updateSectionOrdinals(self: *MachO) !void {
if (!self.sections_order_dirty) return;
@ -5776,8 +5962,11 @@ fn writeDyldInfoData(self: *MachO) !void {
}
const seg = self.load_commands.items[match.seg].segment;
const sect = seg.sections.items[match.sect];
log.warn("dyld info for {s},{s}", .{ sect.segName(), sect.sectName() });
while (true) {
log.warn(" ATOM %{d}", .{atom.local_sym_index});
const sym = self.locals.items[atom.local_sym_index];
const base_offset = sym.n_value - seg.inner.vmaddr;
@ -6217,10 +6406,19 @@ fn writeSymbolTable(self: *MachO) !void {
for (self.locals.items) |sym| {
if (sym.n_strx == 0) continue;
if (sym.n_desc == N_DESC_GCED) continue;
if (self.symbol_resolver.get(sym.n_strx)) |_| continue;
try locals.append(sym);
}
var globals = std.ArrayList(macho.nlist_64).init(self.base.allocator);
defer globals.deinit();
for (self.globals.items) |sym| {
if (sym.n_desc == N_DESC_GCED) continue;
try globals.append(sym);
}
// TODO How do we handle null global symbols in incremental context?
var undefs = std.ArrayList(macho.nlist_64).init(self.base.allocator);
defer undefs.deinit();
@ -6291,7 +6489,7 @@ fn writeSymbolTable(self: *MachO) !void {
}
const nlocals = locals.items.len;
const nexports = self.globals.items.len;
const nexports = globals.items.len;
const nundefs = undefs.items.len;
const locals_off = symtab.symoff;
@ -6302,7 +6500,7 @@ fn writeSymbolTable(self: *MachO) !void {
const exports_off = locals_off + locals_size;
const exports_size = nexports * @sizeOf(macho.nlist_64);
log.debug("writing exported symbols from 0x{x} to 0x{x}", .{ exports_off, exports_size + exports_off });
try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.globals.items), exports_off);
try self.base.file.?.pwriteAll(mem.sliceAsBytes(globals.items), exports_off);
const undefs_off = exports_off + exports_size;
const undefs_size = nundefs * @sizeOf(macho.nlist_64);
@ -6898,55 +7096,55 @@ fn snapshotState(self: *MachO) !void {
}
fn logSymtab(self: MachO) void {
log.debug("locals:", .{});
log.warn("locals:", .{});
for (self.locals.items) |sym, id| {
log.debug(" {d}: {s}: @{x} in {d}", .{ id, self.getString(sym.n_strx), sym.n_value, sym.n_sect });
log.warn(" {d}: {s}: @{x} in {d}", .{ id, self.getString(sym.n_strx), sym.n_value, sym.n_sect });
}
log.debug("globals:", .{});
log.warn("globals:", .{});
for (self.globals.items) |sym, id| {
log.debug(" {d}: {s}: @{x} in {d}", .{ id, self.getString(sym.n_strx), sym.n_value, sym.n_sect });
log.warn(" {d}: {s}: @{x} in {d}", .{ id, self.getString(sym.n_strx), sym.n_value, sym.n_sect });
}
log.debug("undefs:", .{});
log.warn("undefs:", .{});
for (self.undefs.items) |sym, id| {
log.debug(" {d}: {s}: in {d}", .{ id, self.getString(sym.n_strx), sym.n_desc });
log.warn(" {d}: {s}: in {d}", .{ id, self.getString(sym.n_strx), sym.n_desc });
}
{
log.debug("resolver:", .{});
log.warn("resolver:", .{});
var it = self.symbol_resolver.iterator();
while (it.next()) |entry| {
log.debug(" {s} => {}", .{ self.getString(entry.key_ptr.*), entry.value_ptr.* });
log.warn(" {s} => {}", .{ self.getString(entry.key_ptr.*), entry.value_ptr.* });
}
}
log.debug("GOT entries:", .{});
log.warn("GOT entries:", .{});
for (self.got_entries_table.values()) |value| {
const key = self.got_entries.items[value].target;
const atom = self.got_entries.items[value].atom;
const n_value = self.locals.items[atom.local_sym_index].n_value;
switch (key) {
.local => |ndx| log.debug(" {d}: @{x}", .{ ndx, n_value }),
.global => |n_strx| log.debug(" {s}: @{x}", .{ self.getString(n_strx), n_value }),
.local => |ndx| log.warn(" {d}: @{x}", .{ ndx, n_value }),
.global => |n_strx| log.warn(" {s}: @{x}", .{ self.getString(n_strx), n_value }),
}
}
log.debug("__thread_ptrs entries:", .{});
log.warn("__thread_ptrs entries:", .{});
for (self.tlv_ptr_entries_table.values()) |value| {
const key = self.tlv_ptr_entries.items[value].target;
const atom = self.tlv_ptr_entries.items[value].atom;
const n_value = self.locals.items[atom.local_sym_index].n_value;
assert(key == .global);
log.debug(" {s}: @{x}", .{ self.getString(key.global), n_value });
log.warn(" {s}: @{x}", .{ self.getString(key.global), n_value });
}
log.debug("stubs:", .{});
log.warn("stubs:", .{});
for (self.stubs_table.keys()) |key| {
const value = self.stubs_table.get(key).?;
const atom = self.stubs.items[value];
const sym = self.locals.items[atom.local_sym_index];
log.debug(" {s}: @{x}", .{ self.getString(key), sym.n_value });
log.warn(" {s}: @{x}", .{ self.getString(key), sym.n_value });
}
}
@ -6964,6 +7162,45 @@ fn logSectionOrdinals(self: MachO) void {
}
}
fn logAtoms(self: MachO) void {
log.warn("atoms:", .{});
var it = self.atoms.iterator();
while (it.next()) |entry| {
const match = entry.key_ptr.*;
var atom = entry.value_ptr.*;
while (atom.prev) |prev| {
atom = prev;
}
const seg = self.load_commands.items[match.seg].segment;
const sect = seg.sections.items[match.sect];
log.warn("{s},{s}", .{ sect.segName(), sect.sectName() });
while (true) {
self.logAtom(atom);
if (atom.next) |next| {
atom = next;
} else break;
}
}
}
fn logAtom(self: MachO, atom: *const Atom) void {
const sym = self.locals.items[atom.local_sym_index];
log.warn(" ATOM(%{d}) @ {x}", .{ atom.local_sym_index, sym.n_value });
for (atom.contained.items) |sym_off| {
const inner_sym = self.locals.items[sym_off.local_sym_index];
log.warn(" %{d} ('{s}') @ {x}", .{
sym_off.local_sym_index,
self.getString(inner_sym.n_strx),
inner_sym.n_value,
});
}
}
/// Since `os.copy_file_range` cannot be used when copying overlapping ranges within the same file,
/// and since `File.copyRangeAll` uses `os.copy_file_range` under-the-hood, we use heap allocated
/// buffers on all hosts except Linux (if `copy_file_range` syscall is available).

View File

@ -236,6 +236,7 @@ pub fn freeListEligible(self: Atom, macho_file: MachO) bool {
const RelocContext = struct {
base_addr: u64 = 0,
base_offset: i32 = 0,
allocator: Allocator,
object: *Object,
macho_file: *MachO,
@ -366,7 +367,7 @@ pub fn parseRelocs(self: *Atom, relocs: []const macho.relocation_info, context:
) orelse unreachable;
break :target Relocation.Target{ .global = n_strx };
};
const offset = @intCast(u32, rel.r_address);
const offset = @intCast(u32, rel.r_address - context.base_offset);
switch (arch) {
.aarch64 => {
@ -487,7 +488,7 @@ fn addPtrBindingOrRebase(
.global => |n_strx| {
try self.bindings.append(context.allocator, .{
.n_strx = n_strx,
.offset = @intCast(u32, rel.r_address),
.offset = @intCast(u32, rel.r_address - context.base_offset),
});
},
.local => {
@ -529,7 +530,10 @@ fn addPtrBindingOrRebase(
};
if (should_rebase) {
try self.rebases.append(context.allocator, @intCast(u32, rel.r_address));
try self.rebases.append(
context.allocator,
@intCast(u32, rel.r_address - context.base_offset),
);
}
},
}
@ -650,6 +654,60 @@ fn addStub(target: Relocation.Target, context: RelocContext) !void {
context.macho_file.stubs.items[stub_index] = atom;
}
pub fn getTargetAtom(rel: Relocation, macho_file: *MachO) !?*Atom {
const is_via_got = got: {
switch (macho_file.base.options.target.cpu.arch) {
.aarch64 => break :got switch (@intToEnum(macho.reloc_type_arm64, rel.@"type")) {
.ARM64_RELOC_GOT_LOAD_PAGE21,
.ARM64_RELOC_GOT_LOAD_PAGEOFF12,
.ARM64_RELOC_POINTER_TO_GOT,
=> true,
else => false,
},
.x86_64 => break :got switch (@intToEnum(macho.reloc_type_x86_64, rel.@"type")) {
.X86_64_RELOC_GOT, .X86_64_RELOC_GOT_LOAD => true,
else => false,
},
else => unreachable,
}
};
if (is_via_got) {
const got_index = macho_file.got_entries_table.get(rel.target) orelse {
log.err("expected GOT entry for symbol", .{});
switch (rel.target) {
.local => |sym_index| log.err(" local @{d}", .{sym_index}),
.global => |n_strx| log.err(" global @'{s}'", .{macho_file.getString(n_strx)}),
}
log.err(" this is an internal linker error", .{});
return error.FailedToResolveRelocationTarget;
};
return macho_file.got_entries.items[got_index].atom;
}
switch (rel.target) {
.local => |sym_index| {
return macho_file.atom_by_index_table.get(sym_index);
},
.global => |n_strx| {
const resolv = macho_file.symbol_resolver.get(n_strx).?;
switch (resolv.where) {
.global => return macho_file.atom_by_index_table.get(resolv.local_sym_index),
.undef => {
if (macho_file.stubs_table.get(n_strx)) |stub_index| {
return macho_file.stubs.items[stub_index];
} else {
if (macho_file.tlv_ptr_entries_table.get(rel.target)) |tlv_ptr_index| {
return macho_file.tlv_ptr_entries.items[tlv_ptr_index].atom;
}
return null;
}
},
}
},
}
}
pub fn resolveRelocs(self: *Atom, macho_file: *MachO) !void {
const tracy = trace(@src());
defer tracy.end();

View File

@ -176,6 +176,13 @@ pub fn free(self: *Object, allocator: Allocator, macho_file: *MachO) void {
.n_desc = 0,
.n_value = 0,
};
_ = macho_file.atom_by_index_table.remove(atom.local_sym_index);
_ = macho_file.gc_roots.remove(atom);
for (atom.contained.items) |sym_off| {
_ = macho_file.atom_by_index_table.remove(sym_off.local_sym_index);
}
atom.local_sym_index = 0;
}
if (atom == last_atom) {
@ -346,7 +353,7 @@ const NlistWithIndex = struct {
}
}
fn filterInSection(symbols: []NlistWithIndex, sect: macho.section_64) []NlistWithIndex {
fn filterByAddress(symbols: []NlistWithIndex, start_addr: u64, end_addr: u64) []NlistWithIndex {
const Predicate = struct {
addr: u64,
@ -355,13 +362,36 @@ const NlistWithIndex = struct {
}
};
const start = MachO.findFirst(NlistWithIndex, symbols, 0, Predicate{ .addr = sect.addr });
const end = MachO.findFirst(NlistWithIndex, symbols, start, Predicate{ .addr = sect.addr + sect.size });
const start = MachO.findFirst(NlistWithIndex, symbols, 0, Predicate{
.addr = start_addr,
});
const end = MachO.findFirst(NlistWithIndex, symbols, start, Predicate{
.addr = end_addr,
});
return symbols[start..end];
}
};
fn filterRelocs(
relocs: []const macho.relocation_info,
start_addr: u64,
end_addr: u64,
) []const macho.relocation_info {
const Predicate = struct {
addr: u64,
pub fn predicate(self: @This(), rel: macho.relocation_info) bool {
return rel.r_address < self.addr;
}
};
const start = MachO.findFirst(macho.relocation_info, relocs, 0, Predicate{ .addr = end_addr });
const end = MachO.findFirst(macho.relocation_info, relocs, start, Predicate{ .addr = start_addr });
return relocs[start..end];
}
fn filterDice(
dices: []const macho.data_in_code_entry,
start_addr: u64,
@ -422,16 +452,13 @@ pub fn parseIntoAtoms(self: *Object, allocator: Allocator, macho_file: *MachO) !
// We only care about defined symbols, so filter every other out.
const sorted_nlists = sorted_all_nlists.items[0..iundefsym];
const dead_strip = blk: {
const dead_strip = macho_file.base.options.gc_sections orelse break :blk false;
if (dead_strip or macho_file.base.options.optimize_mode != .Debug)
break :blk self.header.flags & macho.MH_SUBSECTIONS_VIA_SYMBOLS != 0;
break :blk false;
};
const dead_strip = macho_file.base.options.gc_sections orelse false;
const subsections_via_symbols = self.header.flags & macho.MH_SUBSECTIONS_VIA_SYMBOLS != 0 and
(macho_file.base.options.optimize_mode != .Debug or dead_strip);
for (seg.sections.items) |sect, id| {
const sect_id = @intCast(u8, id);
log.debug("putting section '{s},{s}' as an Atom", .{ sect.segName(), sect.sectName() });
log.debug("parsing section '{s},{s}' into Atoms", .{ sect.segName(), sect.sectName() });
// Get matching segment/section in the final artifact.
const match = (try macho_file.getMatchingSection(sect)) orelse {
@ -455,7 +482,11 @@ pub fn parseIntoAtoms(self: *Object, allocator: Allocator, macho_file: *MachO) !
);
// Symbols within this section only.
const filtered_nlists = NlistWithIndex.filterInSection(sorted_nlists, sect);
const filtered_nlists = NlistWithIndex.filterByAddress(
sorted_nlists,
sect.addr,
sect.addr + sect.size,
);
macho_file.has_dices = macho_file.has_dices or blk: {
if (self.text_section_index) |index| {
@ -467,204 +498,123 @@ pub fn parseIntoAtoms(self: *Object, allocator: Allocator, macho_file: *MachO) !
};
macho_file.has_stabs = macho_file.has_stabs or self.debug_info != null;
if (dead_strip) blk: {
if (filtered_nlists.len == 0) break :blk; // nothing to split
if (subsections_via_symbols and filtered_nlists.len > 0) {
// If the first nlist does not match the start of the section,
// then we need to encapsulate the memory range [section start, first symbol)
// as a temporary symbol and insert the matching Atom.
const first_nlist = filtered_nlists[0].nlist;
if (first_nlist.n_value > sect.addr) {}
}
if (first_nlist.n_value > sect.addr) {
const local_sym_index = self.sections_as_symbols.get(sect_id) orelse blk: {
const local_sym_index = @intCast(u32, macho_file.locals.items.len);
try macho_file.locals.append(allocator, .{
.n_strx = 0,
.n_type = macho.N_SECT,
.n_sect = @intCast(u8, macho_file.section_ordinals.getIndex(match).? + 1),
.n_desc = 0,
.n_value = sect.addr,
});
try self.sections_as_symbols.putNoClobber(allocator, sect_id, local_sym_index);
break :blk local_sym_index;
};
const atom_size = first_nlist.n_value - sect.addr;
const atom_code: ?[]const u8 = if (code) |cc|
cc[0..atom_size]
else
null;
try self.parseIntoAtom(
allocator,
local_sym_index,
atom_size,
sect.@"align",
atom_code,
relocs,
&.{},
match,
sect,
macho_file,
);
}
// If there is no symbol to refer to this atom, we create
// a temp one, unless we already did that when working out the relocations
// of other atoms.
const local_sym_index = self.sections_as_symbols.get(sect_id) orelse blk: {
const local_sym_index = @intCast(u32, macho_file.locals.items.len);
try macho_file.locals.append(allocator, .{
.n_strx = 0,
.n_type = macho.N_SECT,
.n_sect = @intCast(u8, macho_file.section_ordinals.getIndex(match).? + 1),
.n_desc = 0,
.n_value = sect.addr,
});
try self.sections_as_symbols.putNoClobber(allocator, sect_id, local_sym_index);
break :blk local_sym_index;
};
const atom = try self.parseIntoAtom(
allocator,
local_sym_index,
sect.size,
sect.@"align",
code,
relocs,
filtered_nlists,
match,
macho_file,
);
var next_nlist_count: usize = 0;
while (next_nlist_count < filtered_nlists.len) {
const next_nlist = filtered_nlists[next_nlist_count];
const addr = next_nlist.nlist.n_value;
const atom_nlists = NlistWithIndex.filterByAddress(
filtered_nlists[next_nlist_count..],
addr,
addr + 1,
);
next_nlist_count += atom_nlists.len;
if (!self.start_atoms.contains(match)) {
try self.start_atoms.putNoClobber(allocator, match, atom);
}
const local_sym_index = @intCast(u32, macho_file.locals.items.len);
try macho_file.locals.append(allocator, .{
.n_strx = 0,
.n_type = macho.N_SECT,
.n_sect = @intCast(u8, macho_file.section_ordinals.getIndex(match).? + 1),
.n_desc = 0,
.n_value = addr,
});
if (self.end_atoms.getPtr(match)) |last| {
last.*.next = atom;
atom.prev = last.*;
last.* = atom;
const atom_size = blk: {
const end_addr = if (next_nlist_count < filtered_nlists.len)
filtered_nlists[next_nlist_count].nlist.n_value
else
sect.addr + sect.size;
break :blk end_addr - addr;
};
const atom_code: ?[]const u8 = if (code) |cc|
cc[addr - sect.addr ..][0..atom_size]
else
null;
const atom_align = if (addr > 0)
math.min(@ctz(u64, addr), sect.@"align")
else
sect.@"align";
try self.parseIntoAtom(
allocator,
local_sym_index,
atom_size,
atom_align,
atom_code,
relocs,
atom_nlists,
match,
sect,
macho_file,
);
}
} else {
try self.end_atoms.putNoClobber(allocator, match, atom);
// If there is no symbol to refer to this atom, we create
// a temp one, unless we already did that when working out the relocations
// of other atoms.
const local_sym_index = self.sections_as_symbols.get(sect_id) orelse blk: {
const local_sym_index = @intCast(u32, macho_file.locals.items.len);
try macho_file.locals.append(allocator, .{
.n_strx = 0,
.n_type = macho.N_SECT,
.n_sect = @intCast(u8, macho_file.section_ordinals.getIndex(match).? + 1),
.n_desc = 0,
.n_value = sect.addr,
});
try self.sections_as_symbols.putNoClobber(allocator, sect_id, local_sym_index);
break :blk local_sym_index;
};
try self.parseIntoAtom(
allocator,
local_sym_index,
sect.size,
sect.@"align",
code,
relocs,
filtered_nlists,
match,
sect,
macho_file,
);
}
try self.contained_atoms.append(allocator, atom);
}
}
// const Context = struct {
// allocator: *Allocator,
// object: *Object,
// macho_file: *MachO,
// match: MachO.MatchingSection,
// };
// const AtomParser = struct {
// section: macho.section_64,
// code: []u8,
// relocs: []macho.relocation_info,
// nlists: []NlistWithIndex,
// index: u32 = 0,
// fn peek(self: AtomParser) ?NlistWithIndex {
// return if (self.index + 1 < self.nlists.len) self.nlists[self.index + 1] else null;
// }
// fn lessThanBySeniority(context: Context, lhs: NlistWithIndex, rhs: NlistWithIndex) bool {
// if (!MachO.symbolIsExt(rhs.nlist)) {
// return MachO.symbolIsTemp(lhs.nlist, context.object.getString(lhs.nlist.n_strx));
// } else if (MachO.symbolIsPext(rhs.nlist) or MachO.symbolIsWeakDef(rhs.nlist)) {
// return !MachO.symbolIsExt(lhs.nlist);
// } else {
// return false;
// }
// }
// pub fn next(self: *AtomParser, context: Context) !?*Atom {
// if (self.index == self.nlists.len) return null;
// const tracy = trace(@src());
// defer tracy.end();
// var aliases = std.ArrayList(NlistWithIndex).init(context.allocator);
// defer aliases.deinit();
// const next_nlist: ?NlistWithIndex = blk: while (true) {
// const curr_nlist = self.nlists[self.index];
// try aliases.append(curr_nlist);
// if (self.peek()) |next_nlist| {
// if (curr_nlist.nlist.n_value == next_nlist.nlist.n_value) {
// self.index += 1;
// continue;
// }
// break :blk next_nlist;
// }
// break :blk null;
// } else null;
// for (aliases.items) |*nlist_with_index| {
// nlist_with_index.index = context.object.symbol_mapping.get(nlist_with_index.index) orelse unreachable;
// }
// if (aliases.items.len > 1) {
// // Bubble-up senior symbol as the main link to the atom.
// sort.sort(
// NlistWithIndex,
// aliases.items,
// context,
// AtomParser.lessThanBySeniority,
// );
// }
// const senior_nlist = aliases.pop();
// const senior_sym = &context.macho_file.locals.items[senior_nlist.index];
// senior_sym.n_sect = @intCast(u8, context.macho_file.section_ordinals.getIndex(context.match).? + 1);
// const start_addr = senior_nlist.nlist.n_value - self.section.addr;
// const end_addr = if (next_nlist) |n| n.nlist.n_value - self.section.addr else self.section.size;
// const code = self.code[start_addr..end_addr];
// const size = code.len;
// const max_align = self.section.@"align";
// const actual_align = if (senior_nlist.nlist.n_value > 0)
// math.min(@ctz(u64, senior_nlist.nlist.n_value), max_align)
// else
// max_align;
// const stab: ?Atom.Stab = if (context.object.debug_info) |di| blk: {
// // TODO there has to be a better to handle this.
// for (di.inner.func_list.items) |func| {
// if (func.pc_range) |range| {
// if (senior_nlist.nlist.n_value >= range.start and senior_nlist.nlist.n_value < range.end) {
// break :blk Atom.Stab{
// .function = range.end - range.start,
// };
// }
// }
// }
// // TODO
// // if (self.macho_file.globals.contains(self.macho_file.getString(senior_sym.strx))) break :blk .global;
// break :blk .static;
// } else null;
// const atom = try context.macho_file.createEmptyAtom(senior_nlist.index, size, actual_align);
// atom.stab = stab;
// const is_zerofill = blk: {
// const section_type = commands.sectionType(self.section);
// break :blk section_type == macho.S_ZEROFILL or section_type == macho.S_THREAD_LOCAL_ZEROFILL;
// };
// if (!is_zerofill) {
// mem.copy(u8, atom.code.items, code);
// }
// try atom.aliases.ensureTotalCapacity(context.allocator, aliases.items.len);
// for (aliases.items) |alias| {
// atom.aliases.appendAssumeCapacity(alias.index);
// const sym = &context.macho_file.locals.items[alias.index];
// sym.n_sect = @intCast(u8, context.macho_file.section_ordinals.getIndex(context.match).? + 1);
// }
// try atom.parseRelocs(self.relocs, .{
// .base_addr = self.section.addr,
// .base_offset = start_addr,
// .allocator = context.allocator,
// .object = context.object,
// .macho_file = context.macho_file,
// });
// if (context.macho_file.has_dices) {
// const dices = filterDice(
// context.object.data_in_code_entries.items,
// senior_nlist.nlist.n_value,
// senior_nlist.nlist.n_value + size,
// );
// try atom.dices.ensureTotalCapacity(context.allocator, dices.len);
// for (dices) |dice| {
// atom.dices.appendAssumeCapacity(.{
// .offset = dice.offset - try math.cast(u32, senior_nlist.nlist.n_value),
// .length = dice.length,
// .kind = dice.kind,
// });
// }
// }
// self.index += 1;
// return atom;
// }
// };
fn parseIntoAtom(
self: *Object,
allocator: Allocator,
@ -675,8 +625,9 @@ fn parseIntoAtom(
relocs: []const macho.relocation_info,
nlists: []const NlistWithIndex,
match: MatchingSection,
sect: macho.section_64,
macho_file: *MachO,
) !*Atom {
) !void {
const sym = macho_file.locals.items[local_sym_index];
const align_pow_2 = try math.powi(u32, 2, alignment);
const aligned_size = mem.alignForwardGeneric(u64, size, align_pow_2);
@ -686,8 +637,11 @@ fn parseIntoAtom(
mem.copy(u8, atom.code.items, cc);
}
try atom.parseRelocs(relocs, .{
.base_addr = sym.n_value,
const base_offset = sym.n_value - sect.addr;
const filtered_relocs = filterRelocs(relocs, base_offset, base_offset + size);
try atom.parseRelocs(filtered_relocs, .{
.base_addr = sect.addr,
.base_offset = @intCast(i32, base_offset),
.allocator = allocator,
.object = self,
.macho_file = macho_file,
@ -740,9 +694,41 @@ fn parseIntoAtom(
.offset = nlist.n_value - sym.n_value,
.stab = stab,
});
try macho_file.atom_by_index_table.putNoClobber(allocator, sym_index, atom);
}
return atom;
const is_gc_root = blk: {
if (sect.isDontDeadStrip()) break :blk true;
if (sect.isDontDeadStripIfReferencesLive()) {
// TODO if isDontDeadStripIfReferencesLive we should analyse the edges
// before making it a GC root
break :blk true;
}
if (mem.eql(u8, "__StaticInit", sect.sectName())) break :blk true;
switch (sect.type_()) {
macho.S_MOD_INIT_FUNC_POINTERS,
macho.S_MOD_TERM_FUNC_POINTERS,
=> break :blk true,
else => break :blk false,
}
};
if (is_gc_root) {
try macho_file.gc_roots.putNoClobber(allocator, atom, {});
}
if (!self.start_atoms.contains(match)) {
try self.start_atoms.putNoClobber(allocator, match, atom);
}
if (self.end_atoms.getPtr(match)) |last| {
last.*.next = atom;
atom.prev = last.*;
last.* = atom;
} else {
try self.end_atoms.putNoClobber(allocator, match, atom);
}
try self.contained_atoms.append(allocator, atom);
}
fn parseSymtab(self: *Object) void {

View File

@ -16,6 +16,7 @@ pub fn build(b: *Builder) void {
// TODO when we figure out how to ship framework stubs for cross-compilation,
// populate paths to the sysroot here.
exe.linkFramework("Foundation");
exe.link_gc_sections = true;
const run_cmd = exe.run();
run_cmd.expectStdOutEqual("Hello from C++ and Zig");