macho: move findFreeSpace into SegmentCommand

One exception will be treatment of the __LINKEDIT segment which
will be handled separately inside MachO directly since it doesn't
include any sections.
This commit is contained in:
Jakub Konka
2020-12-26 20:55:11 +01:00
parent 595397dbeb
commit e1451f92f8
2 changed files with 142 additions and 130 deletions

View File

@@ -155,8 +155,8 @@ pub const PieFixup = struct {
};
/// `alloc_num / alloc_den` is the factor of padding when allocating.
const alloc_num = 4;
const alloc_den = 3;
pub const alloc_num = 4;
pub const alloc_den = 3;
/// Default path to dyld
/// TODO instead of hardcoding it, we should probably look through some env vars and search paths
@@ -1358,7 +1358,7 @@ pub fn populateMissingMetadata(self: *MachO) !void {
};
const flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS;
const needed_size = self.base.options.program_code_size_hint;
const off = self.findFreeSpace(text_segment, needed_size, @as(u16, 1) << alignment);
const off = text_segment.findFreeSpace(needed_size, @as(u16, 1) << alignment, self.header_pad);
log.debug("found __text section free space 0x{x} to 0x{x}", .{ off, off + needed_size });
@@ -1386,7 +1386,7 @@ pub fn populateMissingMetadata(self: *MachO) !void {
const flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS;
const needed_size = @sizeOf(u64) * self.base.options.symbol_count_hint;
const off = self.findFreeSpace(text_segment, needed_size, @alignOf(u64));
const off = text_segment.findFreeSpace(needed_size, @alignOf(u64), self.header_pad);
assert(off + needed_size <= text_segment.inner.fileoff + text_segment.inner.filesize); // TODO Must expand __TEXT segment.
log.debug("found __ziggot section free space 0x{x} to 0x{x}", .{ off, off + needed_size });
@@ -1437,11 +1437,10 @@ pub fn populateMissingMetadata(self: *MachO) !void {
}
if (self.dyld_info_cmd_index == null) {
self.dyld_info_cmd_index = @intCast(u16, self.load_commands.items.len);
const linkedit_segment = self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
// TODO Preallocate rebase, binding, and lazy binding info.
const export_size = 2;
const export_off = self.findFreeSpace(&linkedit_segment, export_size, 1);
const export_off = self.findFreeSpaceLinkedit(export_size, 1);
log.debug("found export info free space 0x{x} to 0x{x}", .{ export_off, export_off + export_size });
@@ -1466,16 +1465,15 @@ pub fn populateMissingMetadata(self: *MachO) !void {
}
if (self.symtab_cmd_index == null) {
self.symtab_cmd_index = @intCast(u16, self.load_commands.items.len);
const linkedit_segment = self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
const symtab_size = self.base.options.symbol_count_hint * @sizeOf(macho.nlist_64);
const symtab_off = self.findFreeSpace(&linkedit_segment, symtab_size, @sizeOf(macho.nlist_64));
const symtab_off = self.findFreeSpaceLinkedit(symtab_size, @sizeOf(macho.nlist_64));
log.debug("found symbol table free space 0x{x} to 0x{x}", .{ symtab_off, symtab_off + symtab_size });
try self.string_table.append(self.base.allocator, 0); // Need a null at position 0.
const strtab_size = self.string_table.items.len;
const strtab_off = self.findFreeSpace(&linkedit_segment, strtab_size, 1);
const strtab_off = self.findFreeSpaceLinkedit(strtab_size, 1);
log.debug("found string table free space 0x{x} to 0x{x}", .{ strtab_off, strtab_off + strtab_size });
@@ -1613,7 +1611,6 @@ pub fn populateMissingMetadata(self: *MachO) !void {
}
if (self.code_signature_cmd_index == null) {
self.code_signature_cmd_index = @intCast(u16, self.load_commands.items.len);
const linkedit_segment = self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
try self.load_commands.append(self.base.allocator, .{
.LinkeditData = .{
.cmd = macho.LC_CODE_SIGNATURE,
@@ -1790,48 +1787,41 @@ fn nextSegmentAddressAndOffset(self: *MachO) NextSegmentAddressAndOffset {
};
}
fn allocatedSize(self: *MachO, segment: *const SegmentCommand, start: u64) u64 {
fn allocatedSizeLinkedit(self: *MachO, start: u64) u64 {
assert(start > 0);
var min_pos: u64 = std.math.maxInt(u64);
if (parseAndCmpName(&segment.inner.segname, "__LINKEDIT")) {
assert(segment.sections.items.len == 0);
// __LINKEDIT is a weird segment where sections get their own load commands so we
// special-case it.
if (self.dyld_info_cmd_index) |idx| {
const dyld_info = self.load_commands.items[idx].DyldInfoOnly;
if (dyld_info.rebase_off > start and dyld_info.rebase_off < min_pos) min_pos = dyld_info.rebase_off;
if (dyld_info.bind_off > start and dyld_info.bind_off < min_pos) min_pos = dyld_info.bind_off;
if (dyld_info.weak_bind_off > start and dyld_info.weak_bind_off < min_pos) min_pos = dyld_info.weak_bind_off;
if (dyld_info.lazy_bind_off > start and dyld_info.lazy_bind_off < min_pos) min_pos = dyld_info.lazy_bind_off;
if (dyld_info.export_off > start and dyld_info.export_off < min_pos) min_pos = dyld_info.export_off;
}
// __LINKEDIT is a weird segment where sections get their own load commands so we
// special-case it.
if (self.dyld_info_cmd_index) |idx| {
const dyld_info = self.load_commands.items[idx].DyldInfoOnly;
if (dyld_info.rebase_off > start and dyld_info.rebase_off < min_pos) min_pos = dyld_info.rebase_off;
if (dyld_info.bind_off > start and dyld_info.bind_off < min_pos) min_pos = dyld_info.bind_off;
if (dyld_info.weak_bind_off > start and dyld_info.weak_bind_off < min_pos) min_pos = dyld_info.weak_bind_off;
if (dyld_info.lazy_bind_off > start and dyld_info.lazy_bind_off < min_pos) min_pos = dyld_info.lazy_bind_off;
if (dyld_info.export_off > start and dyld_info.export_off < min_pos) min_pos = dyld_info.export_off;
}
if (self.function_starts_cmd_index) |idx| {
const fstart = self.load_commands.items[idx].LinkeditData;
if (fstart.dataoff > start and fstart.dataoff < min_pos) min_pos = fstart.dataoff;
}
if (self.function_starts_cmd_index) |idx| {
const fstart = self.load_commands.items[idx].LinkeditData;
if (fstart.dataoff > start and fstart.dataoff < min_pos) min_pos = fstart.dataoff;
}
if (self.data_in_code_cmd_index) |idx| {
const dic = self.load_commands.items[idx].LinkeditData;
if (dic.dataoff > start and dic.dataoff < min_pos) min_pos = dic.dataoff;
}
if (self.data_in_code_cmd_index) |idx| {
const dic = self.load_commands.items[idx].LinkeditData;
if (dic.dataoff > start and dic.dataoff < min_pos) min_pos = dic.dataoff;
}
if (self.dysymtab_cmd_index) |idx| {
const dysymtab = self.load_commands.items[idx].Dysymtab;
if (dysymtab.indirectsymoff > start and dysymtab.indirectsymoff < min_pos) min_pos = dysymtab.indirectsymoff;
// TODO Handle more dynamic symbol table sections.
}
if (self.dysymtab_cmd_index) |idx| {
const dysymtab = self.load_commands.items[idx].Dysymtab;
if (dysymtab.indirectsymoff > start and dysymtab.indirectsymoff < min_pos) min_pos = dysymtab.indirectsymoff;
// TODO Handle more dynamic symbol table sections.
}
if (self.symtab_cmd_index) |idx| {
const symtab = self.load_commands.items[idx].Symtab;
if (symtab.symoff > start and symtab.symoff < min_pos) min_pos = symtab.symoff;
if (symtab.stroff > start and symtab.stroff < min_pos) min_pos = symtab.stroff;
}
} else {
for (segment.sections.items) |section| {
if (section.offset > start and section.offset < min_pos) min_pos = section.offset;
}
if (self.symtab_cmd_index) |idx| {
const symtab = self.load_commands.items[idx].Symtab;
if (symtab.symoff > start and symtab.symoff < min_pos) min_pos = symtab.symoff;
if (symtab.stroff > start and symtab.stroff < min_pos) min_pos = symtab.stroff;
}
return min_pos - start;
@@ -1846,101 +1836,90 @@ inline fn checkForCollision(start: u64, end: u64, off: u64, size: u64) ?u64 {
return null;
}
fn detectAllocCollision(self: *MachO, segment: *const SegmentCommand, start: u64, size: u64) ?u64 {
fn detectAllocCollisionLinkedit(self: *MachO, start: u64, size: u64) ?u64 {
const end = start + satMul(size, alloc_num) / alloc_den;
if (parseAndCmpName(&segment.inner.segname, "__LINKEDIT")) {
assert(segment.sections.items.len == 0);
// __LINKEDIT is a weird segment where sections get their own load commands so we
// special-case it.
if (self.dyld_info_cmd_index) |idx| outer: {
if (self.load_commands.items.len == idx) break :outer;
const dyld_info = self.load_commands.items[idx].DyldInfoOnly;
if (checkForCollision(start, end, dyld_info.rebase_off, dyld_info.rebase_size)) |pos| {
return pos;
}
// Binding info
if (checkForCollision(start, end, dyld_info.bind_off, dyld_info.bind_size)) |pos| {
return pos;
}
// Weak binding info
if (checkForCollision(start, end, dyld_info.weak_bind_off, dyld_info.weak_bind_size)) |pos| {
return pos;
}
// Lazy binding info
if (checkForCollision(start, end, dyld_info.lazy_bind_off, dyld_info.lazy_bind_size)) |pos| {
return pos;
}
// Export info
if (checkForCollision(start, end, dyld_info.export_off, dyld_info.export_size)) |pos| {
return pos;
}
// __LINKEDIT is a weird segment where sections get their own load commands so we
// special-case it.
if (self.dyld_info_cmd_index) |idx| outer: {
if (self.load_commands.items.len == idx) break :outer;
const dyld_info = self.load_commands.items[idx].DyldInfoOnly;
if (checkForCollision(start, end, dyld_info.rebase_off, dyld_info.rebase_size)) |pos| {
return pos;
}
// Binding info
if (checkForCollision(start, end, dyld_info.bind_off, dyld_info.bind_size)) |pos| {
return pos;
}
// Weak binding info
if (checkForCollision(start, end, dyld_info.weak_bind_off, dyld_info.weak_bind_size)) |pos| {
return pos;
}
// Lazy binding info
if (checkForCollision(start, end, dyld_info.lazy_bind_off, dyld_info.lazy_bind_size)) |pos| {
return pos;
}
// Export info
if (checkForCollision(start, end, dyld_info.export_off, dyld_info.export_size)) |pos| {
return pos;
}
}
if (self.function_starts_cmd_index) |idx| outer: {
if (self.load_commands.items.len == idx) break :outer;
const fstart = self.load_commands.items[idx].LinkeditData;
if (checkForCollision(start, end, fstart.dataoff, fstart.datasize)) |pos| {
return pos;
}
if (self.function_starts_cmd_index) |idx| outer: {
if (self.load_commands.items.len == idx) break :outer;
const fstart = self.load_commands.items[idx].LinkeditData;
if (checkForCollision(start, end, fstart.dataoff, fstart.datasize)) |pos| {
return pos;
}
}
if (self.data_in_code_cmd_index) |idx| outer: {
if (self.load_commands.items.len == idx) break :outer;
const dic = self.load_commands.items[idx].LinkeditData;
if (checkForCollision(start, end, dic.dataoff, dic.datasize)) |pos| {
return pos;
}
if (self.data_in_code_cmd_index) |idx| outer: {
if (self.load_commands.items.len == idx) break :outer;
const dic = self.load_commands.items[idx].LinkeditData;
if (checkForCollision(start, end, dic.dataoff, dic.datasize)) |pos| {
return pos;
}
}
if (self.dysymtab_cmd_index) |idx| outer: {
if (self.load_commands.items.len == idx) break :outer;
const dysymtab = self.load_commands.items[idx].Dysymtab;
// Indirect symbol table
const nindirectsize = dysymtab.nindirectsyms * @sizeOf(u32);
if (checkForCollision(start, end, dysymtab.indirectsymoff, nindirectsize)) |pos| {
return pos;
}
// TODO Handle more dynamic symbol table sections.
if (self.dysymtab_cmd_index) |idx| outer: {
if (self.load_commands.items.len == idx) break :outer;
const dysymtab = self.load_commands.items[idx].Dysymtab;
// Indirect symbol table
const nindirectsize = dysymtab.nindirectsyms * @sizeOf(u32);
if (checkForCollision(start, end, dysymtab.indirectsymoff, nindirectsize)) |pos| {
return pos;
}
// TODO Handle more dynamic symbol table sections.
}
if (self.symtab_cmd_index) |idx| outer: {
if (self.load_commands.items.len == idx) break :outer;
const symtab = self.load_commands.items[idx].Symtab;
// Symbol table
const symsize = symtab.nsyms * @sizeOf(macho.nlist_64);
if (checkForCollision(start, end, symtab.symoff, symsize)) |pos| {
return pos;
}
// String table
if (checkForCollision(start, end, symtab.stroff, symtab.strsize)) |pos| {
return pos;
}
if (self.symtab_cmd_index) |idx| outer: {
if (self.load_commands.items.len == idx) break :outer;
const symtab = self.load_commands.items[idx].Symtab;
// Symbol table
const symsize = symtab.nsyms * @sizeOf(macho.nlist_64);
if (checkForCollision(start, end, symtab.symoff, symsize)) |pos| {
return pos;
}
} else {
for (segment.sections.items) |section| {
if (checkForCollision(start, end, section.offset, section.size)) |pos| {
return pos;
}
// String table
if (checkForCollision(start, end, symtab.stroff, symtab.strsize)) |pos| {
return pos;
}
}
return null;
}
fn findFreeSpace(self: *MachO, segment: *const SegmentCommand, object_size: u64, min_alignment: u16) u64 {
var start: u64 = if (parseAndCmpName(&segment.inner.segname, "__TEXT"))
self.header_pad
else
segment.inner.fileoff;
while (self.detectAllocCollision(segment, start, object_size)) |item_end| {
fn findFreeSpaceLinkedit(self: *MachO, object_size: u64, min_alignment: u16) u64 {
const linkedit = self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
var start: u64 = linkedit.inner.fileoff;
while (self.detectAllocCollisionLinkedit(start, object_size)) |item_end| {
start = mem.alignForwardGeneric(u64, item_end, min_alignment);
}
return start;
}
/// Saturating multiplication
fn satMul(a: anytype, b: anytype) @TypeOf(a, b) {
pub fn satMul(a: anytype, b: anytype) @TypeOf(a, b) {
const T = @TypeOf(a, b);
return std.math.mul(T, a, b) catch std.math.maxInt(T);
}
@@ -1993,9 +1972,9 @@ fn relocateSymbolTable(self: *MachO) !void {
if (symtab.nsyms < nsyms) {
const linkedit_segment = self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
const needed_size = nsyms * @sizeOf(macho.nlist_64);
if (needed_size > self.allocatedSize(&linkedit_segment, symtab.symoff)) {
if (needed_size > self.allocatedSizeLinkedit(symtab.symoff)) {
// Move the entire symbol table to a new location
const new_symoff = self.findFreeSpace(&linkedit_segment, needed_size, @alignOf(macho.nlist_64));
const new_symoff = self.findFreeSpaceLinkedit(needed_size, @alignOf(macho.nlist_64));
const existing_size = symtab.nsyms * @sizeOf(macho.nlist_64);
log.debug("relocating symbol table from 0x{x}-0x{x} to 0x{x}-0x{x}", .{
@@ -2140,12 +2119,12 @@ fn writeExportTrie(self: *MachO) !void {
const linkedit_segment = self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly;
const allocated_size = self.allocatedSize(&linkedit_segment, dyld_info.export_off);
const allocated_size = self.allocatedSizeLinkedit(dyld_info.export_off);
const needed_size = mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64));
if (needed_size > allocated_size) {
dyld_info.export_off = 0;
dyld_info.export_off = @intCast(u32, self.findFreeSpace(&linkedit_segment, needed_size, 1));
dyld_info.export_off = @intCast(u32, self.findFreeSpaceLinkedit(needed_size, 1));
}
dyld_info.export_size = @intCast(u32, needed_size);
log.debug("writing export info from 0x{x} to 0x{x}", .{ dyld_info.export_off, dyld_info.export_off + dyld_info.export_size });
@@ -2170,12 +2149,12 @@ fn writeBindingInfoTable(self: *MachO) !void {
const linkedit_segment = self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly;
const allocated_size = self.allocatedSize(&linkedit_segment, dyld_info.bind_off);
const allocated_size = self.allocatedSizeLinkedit(dyld_info.bind_off);
const needed_size = mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64));
if (needed_size > allocated_size) {
dyld_info.bind_off = 0;
dyld_info.bind_off = @intCast(u32, self.findFreeSpace(&linkedit_segment, needed_size, 1));
dyld_info.bind_off = @intCast(u32, self.findFreeSpaceLinkedit(needed_size, 1));
}
dyld_info.bind_size = @intCast(u32, needed_size);
@@ -2198,12 +2177,12 @@ fn writeLazyBindingInfoTable(self: *MachO) !void {
const linkedit_segment = self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly;
const allocated_size = self.allocatedSize(&linkedit_segment, dyld_info.lazy_bind_off);
const allocated_size = self.allocatedSizeLinkedit(dyld_info.lazy_bind_off);
const needed_size = mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64));
if (needed_size > allocated_size) {
dyld_info.lazy_bind_off = 0;
dyld_info.lazy_bind_off = @intCast(u32, self.findFreeSpace(&linkedit_segment, needed_size, 1));
dyld_info.lazy_bind_off = @intCast(u32, self.findFreeSpaceLinkedit(needed_size, 1));
}
dyld_info.lazy_bind_size = @intCast(u32, needed_size);
@@ -2222,12 +2201,12 @@ fn writeStringTable(self: *MachO) !void {
const linkedit_segment = self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab;
const allocated_size = self.allocatedSize(&linkedit_segment, symtab.stroff);
const allocated_size = self.allocatedSizeLinkedit(symtab.stroff);
const needed_size = mem.alignForwardGeneric(u64, self.string_table.items.len, @alignOf(u64));
if (needed_size > allocated_size) {
symtab.strsize = 0;
symtab.stroff = @intCast(u32, self.findFreeSpace(&linkedit_segment, needed_size, 1));
symtab.stroff = @intCast(u32, self.findFreeSpaceLinkedit(needed_size, 1));
}
symtab.strsize = @intCast(u32, needed_size);
log.debug("writing string table from 0x{x} to 0x{x}", .{ symtab.stroff, symtab.stroff + symtab.strsize });
@@ -2280,7 +2259,7 @@ fn updateLinkeditSegmentSizes(self: *MachO) !void {
const filesize = final_offset - linkedit_segment.inner.fileoff;
linkedit_segment.inner.filesize = filesize;
linkedit_segment.inner.vmsize = mem.alignForwardGeneric(u64, filesize, self.page_size);
try self.base.file.?.pwriteAll(&[_]u8{ 0 }, final_offset);
try self.base.file.?.pwriteAll(&[_]u8{0}, final_offset);
self.load_commands_dirty = true;
}
@@ -2301,7 +2280,7 @@ fn writeLoadCommands(self: *MachO) !void {
}
const off = @sizeOf(macho.mach_header_64);
log.debug("writing {} load commands from 0x{x} to 0x{x}", .{self.load_commands.items.len, off, off + sizeofcmds});
log.debug("writing {} load commands from 0x{x} to 0x{x}", .{ self.load_commands.items.len, off, off + sizeofcmds });
try self.base.file.?.pwriteAll(buffer, off);
self.load_commands_dirty = false;
}

View File

@@ -7,7 +7,11 @@ const macho = std.macho;
const testing = std.testing;
const Allocator = std.mem.Allocator;
const makeStaticString = @import("../MachO.zig").makeStaticString;
const MachO = @import("../MachO.zig");
const makeStaticString = MachO.makeStaticString;
const satMul = MachO.satMul;
const alloc_num = MachO.alloc_num;
const alloc_den = MachO.alloc_den;
pub const LoadCommand = union(enum) {
Segment: SegmentCommand,
@@ -188,6 +192,35 @@ pub const SegmentCommand = struct {
self.sections.deinit(alloc);
}
pub fn allocatedSize(self: SegmentCommand, start: u64) u64 {
assert(start > 0);
var min_pos: u64 = std.math.maxInt(u64);
for (self.sections.items) |section| {
if (section.offset > start and section.offset < min_pos) min_pos = section.offset;
}
return min_pos - start;
}
fn detectAllocCollision(self: SegmentCommand, start: u64, size: u64) ?u64 {
const end = start + satMul(size, alloc_num) / alloc_den;
for (self.sections.items) |section| {
const increased_size = satMul(section.size, alloc_num) / alloc_den;
const test_end = section.offset + increased_size;
if (end > section.offset and start < test_end) {
return test_end;
}
}
return null;
}
pub fn findFreeSpace(self: SegmentCommand, object_size: u64, min_alignment: u16, start: ?u64) u64 {
var st: u64 = if (start) |v| v else self.inner.fileoff;
while (self.detectAllocCollision(st, object_size)) |item_end| {
st = mem.alignForwardGeneric(u64, item_end, min_alignment);
}
return st;
}
fn eql(self: SegmentCommand, other: SegmentCommand) bool {
if (!meta.eql(self.inner, other.inner)) return false;
const lhs = self.sections.items;